Skip to content

perf: reuse fresh root layout server data instead of refetching it when rendering the error page - #16380

Closed
Nic-Polumeyv wants to merge 3 commits into
sveltejs:version-3from
Nic-Polumeyv:perf-reuse-root-layout-error-data
Closed

perf: reuse fresh root layout server data instead of refetching it when rendering the error page#16380
Nic-Polumeyv wants to merge 3 commits into
sveltejs:version-3from
Nic-Polumeyv:perf-reuse-root-layout-error-data

Conversation

@Nic-Polumeyv

@Nic-Polumeyv Nic-Polumeyv commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

load_root_error_page has carried this TODO since 2022:

// TODO post-https://github.com/sveltejs/kit/discussions/6124 we can use
// existing root layout data

It was written in #6056, and #6174 resolved the discussion it waits on that same month by giving every app a canonical root layout. That PR removed the discussion's sibling TODO elsewhere in client.js but kept this one, so it was left as standing work rather than forgotten. The precondition has been met for four years, so this implements it and deletes the comment.

The error page now reuses current.branch[0].server when it is still fresh instead of fetching __data.json again. Freshness is the same has_changed check normal navigations use to skip valid server nodes, so a root layout load that tracked url, route, a param or a depends key still refetches, and invalidate still forces a fetch. An empty current.branch (SPA-mode initial load, hydration failure) takes the fetch path unchanged. A reused node's load does not run at all, so its side effects (logging, session refresh) skip that render, the same way they already do when navigations skip valid nodes.

Navigations to unknown routes full-reload by design (server_fallback), so the reuse fires where client state is intact, when a navigation's own __data.json request fails or a redirect loops. Today that failure is answered by refetching __data.json on the connection that just failed, then a full page reload. With the reuse the error page renders immediately with root layout data intact. The ssr = false initial load has nothing to reuse and stays a fetch, which is the server-side case #16376 addresses.

The new test intercepts __data.json with a 500 during a client-side navigation and asserts the error page renders with exactly one data request. On version-3 the second request fires and the fallback reload tears the page down.


Please don't delete this checklist! Before submitting the PR, please make sure you do the following:

  • It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
  • This message body should clearly illustrate what problems it solves.
  • Ideally, include a test that fails without this PR but passes with it.

Tests

  • Run the tests with pnpm test and lint the project with pnpm lint and pnpm check

Changesets

  • If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running pnpm changeset and following the prompts. Changesets that add features should be minor and those that fix bugs should be patch. Please prefix changeset messages with feat:, fix:, or chore:.

Edits

  • Please ensure that 'Allow edits from maintainers' is checked. PRs without this option may be closed.

@pkg-svelte-dev

pkg-svelte-dev Bot commented Jul 17, 2026

Copy link
Copy Markdown

Install the latest version of @sveltejs/kit from 53eab5d:

pnpm add https://pkg.svelte.dev/@sveltejs/kit/c/53eab5d8044e87f81ec1c4ca3abb8743a9dfdb34

Open in pkg.svelte.dev: https://pkg.svelte.dev/repos/kit/pr/16380

Note

This PR is from a fork. A maintainer must approve approve each commit before it can be built and installed.

@changeset-bot

changeset-bot Bot commented Jul 17, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 53eab5d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@sveltejs/kit Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@Nic-Polumeyv
Nic-Polumeyv force-pushed the perf-reuse-root-layout-error-data branch from d0795a1 to 2149333 Compare July 17, 2026 03:22
@Nic-Polumeyv
Nic-Polumeyv marked this pull request as ready for review July 17, 2026 03:56
@Nic-Polumeyv
Nic-Polumeyv marked this pull request as draft July 17, 2026 16:08
Rich-Harris pushed a commit that referenced this pull request Jul 22, 2026
…error page (#16381)

After an error page renders, `current.route` is `null`
(`load_root_error_page` stores `route: null` in the navigation state).
The next navigation computes

```js
const route_changed = current.route ? route.id !== current.route.id : false;
```

so the route counts as unchanged no matter where the user goes next. A
root layout server load that tracked `route` is then skipped as fresh,
and its data from before the error page renders on the new page. The new
test's failing assertion on `version-3` shows the layout still claiming
the previous route:

```
Expected: "/b"
Received: "/a"
```

A missing `current.route` now counts as changed. On a first-ever
navigation the flag is irrelevant, the node has no previous data to
reuse anyway, so the only behavior change is the conservative refetch
after error pages.

The interaction was never designed. `route: null` came in #6552 to give
`beforeNavigate` a value meaning "no matched route", and the guard came
two months later in #7450 with route tracking itself, mirroring the
adjacent `url_changed` null-guard. Neither discussed the error-page
case.

The test lives in the no-ssr app. Its root layout gains a server load
returning `route.id`, the navigation's data request is intercepted with
a 500 to produce the error page, and the follow-up navigation must show
the new route id. The app's root `+layout.server.js` was an empty file,
shipped by #11354 so its 404-loop test has a server load node in dev;
giving it a real load keeps that trigger intact.

Found while reviewing #16380, which mirrors this computation for the
error page itself and carries the same one-line fix.

---

### Please don't delete this checklist! Before submitting the PR, please
make sure you do the following:
- [x] It's really useful if your PR references an issue where it is
discussed ahead of time. In many cases, features are absent for a
reason. For large changes, please create an RFC:
https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it
solves.
- [x] Ideally, include a test that fails without this PR but passes with
it.

### Tests
- [x] Run the tests with `pnpm test` and lint the project with `pnpm
lint` and `pnpm check`

### Changesets
- [x] If your PR makes a change that should be noted in one or more
packages' changelogs, generate a changeset by running `pnpm changeset`
and following the prompts. Changesets that add features should be
`minor` and those that fix bugs should be `patch`. Please prefix
changeset messages with `feat:`, `fix:`, or `chore:`.

### Edits

- [x] Please ensure that 'Allow edits from maintainers' is checked. PRs
without this option may be closed.
@Nic-Polumeyv

Copy link
Copy Markdown
Contributor Author

The fetch this skips is also the client's last handoff to the server when only the data route is broken, so the saved request costs the recovery path.

@Nic-Polumeyv
Nic-Polumeyv deleted the perf-reuse-root-layout-error-data branch July 30, 2026 22:53
Rich-Harris pushed a commit that referenced this pull request Aug 18, 2026
…on-existent routes (#16376)

closes #12910

When the client renders an error page for a URL that matches no route,
the root layout's server data goes missing. The universal `+layout.ts`
load receives `data: null` even though the server runs that exact load
function to render the HTML 404. Reported with `ssr = false`, but any
client-side navigation to a non-existent route hits it.

```
GET /this-route-does-not-exist/__data.json?x-sveltekit-invalidated=1

before  404 text/html          (the rendered error page)
after   200 application/json   {"type":"data","nodes":[{"type":"data","data":[{"rootlayout":1},"rootlayout"],"uses":{}}]}
```

`load_root_error_page` fetches `__data.json` when the root layout has a
server load, sending `x-sveltekit-invalidated=1` for the single root
node. The server matches no route and falls through to the `state.depth
=== 0` branch in `respond.js`, which renders the HTML error page. That
branch predates data requests, so they were never special-cased there,
and the render runs the root layout server load only to discard the
result as HTML. Client-side `load_data` then throws `HttpError(404)`,
which since #16135 is deliberately swallowed to avoid a reload loop,
leaving `server_data_node` null.

teemingc diagnosed this on the issue and named two acceptable outcomes,
"We need to be able to return layout data for an error page although a
route doesn't exist or just return the fallback error page." This PR
implements the first, server side only. The client already consumes the
response, and the fallback option would discard data the server computes
anyway while rendering the HTML 404. No client changes.

The new branch only fires for `is_data_request &&
invalidated_data_nodes?.length === 1`. The invalidation parameter marks
kit's own `load_data` fetches, so a browser navigating directly to a
`.../__data.json` URL keeps getting the HTML error page that #15884
deliberately improved (its options-2 test still passes). Length 1 is
exactly the shape `load_root_error_page` sends, so an old deployed
client requesting multi-node data for a route that no longer exists on
the new server keeps today's 404 instead of receiving a truncated nodes
array. Prerendering is excluded so a missing path still reports 404 for
dead-link detection.

The response reuses `render_data` with a synthetic root page (`layouts:
[], leaf: 0`), so load execution, serialization, streaming, redirect and
error nodes all behave as they do for matched routes. Its `route` JSDoc
is narrowed to the two fields it reads instead of casting the synthetic
object to a full `SSRRoute`. Static hosts are unaffected, there is no
server to return the data, and #16135 already keeps the fallback page
from looping there.

The new test in the basics `Errors` describe asserts both halves, JSON
with the parameter and HTML 404 without it. Fails on `version-3` without
the `respond.js` change.

Sibling PR #16380 touches the client half of this flow (reusing fresh
root layout data instead of refetching). The two compose, whichever
lands second rebases trivially.

---

### Please don't delete this checklist! Before submitting the PR, please
make sure you do the following:
- [x] It's really useful if your PR references an issue where it is
discussed ahead of time. In many cases, features are absent for a
reason. For large changes, please create an RFC:
https://github.com/sveltejs/rfcs
- [x] This message body should clearly illustrate what problems it
solves.
- [x] Ideally, include a test that fails without this PR but passes with
it.

### Tests
- [x] Run the tests with `pnpm test` and lint the project with `pnpm
lint` and `pnpm check`

### Changesets
- [x] If your PR makes a change that should be noted in one or more
packages' changelogs, generate a changeset by running `pnpm changeset`
and following the prompts. Changesets that add features should be
`minor` and those that fix bugs should be `patch`. Please prefix
changeset messages with `feat:`, `fix:`, or `chore:`.

### Edits

- [x] Please ensure that 'Allow edits from maintainers' is checked. PRs
without this option may be closed.

---------

Co-authored-by: Nic Polumeyv <nicolas.polum@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant