Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/silent-bushes-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@sveltejs/kit': patch
---

fix: surface prerender errors during development
7 changes: 5 additions & 2 deletions packages/kit/src/runtime/server/page/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ export async function render_page(
// renders an empty 'shell' page if SSR is turned off and if there is
// no server data to prerender. As a result, the load functions and rendering
// only occur client-side.
if (ssr === false && !(state.prerendering && should_prerender_data)) {
if (
ssr === false &&
!((state.prerendering || state.prerender_default === true) && should_prerender_data)
) {
// if the user makes a request through a non-enhanced form, the returned value is lost
// because there is no SSR or client-side handling of the response
if (DEV && action_result && !event.request.headers.has('x-sveltekit-action')) {
Expand Down Expand Up @@ -173,7 +176,7 @@ export async function render_page(

const data_serializer = server_data_serializer(event, event_state, options);
const data_serializer_json =
state.prerendering && should_prerender_data
(state.prerendering || state.prerender_default === true) && should_prerender_data
? server_data_serializer_json(event, event_state, options)
: null;

Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/server/page/load_data.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export async function load_server_data({ event, event_state, state, node, parent
}
);

if (state.prerendering) {
if (state.prerendering || state.prerender_default === true) {
disable_search(url);
}

Expand Down
19 changes: 12 additions & 7 deletions packages/kit/src/runtime/server/page/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export async function render_response({
data_serializer,
error_components
}) {
if (state.prerendering) {
if (state.prerendering || state.prerender_default === true) {
if (options.csp.mode === 'nonce') {
throw new Error('Cannot use prerendering if config.csp.mode === "nonce"');
}
Expand Down Expand Up @@ -106,7 +106,7 @@ export async function render_response({
let base_expression = s(paths.base);

const csp = new Csp(options.csp, {
prerender: !!state.prerendering
prerender: !!(state.prerendering || state.prerender_default === true)
});

// if appropriate, use relative paths for greater portability
Expand Down Expand Up @@ -310,7 +310,7 @@ export async function render_response({
* @param {string[]} attributes
*/
const add_preload = (path, attributes) => {
if (options.link_header_preload && !state.prerendering) {
if (options.link_header_preload && !(state.prerendering || state.prerender_default === true)) {
link_headers.add(`<${encodeURI(path)}>; ${attributes.join('; ')}; nopush`);
} else {
head.add_link_tag(path, attributes);
Expand Down Expand Up @@ -351,7 +351,11 @@ export async function render_response({
if (page_config.ssr && page_config.csr) {
body += `\n\t\t\t${fetched
.map((item) =>
serialize_data(item, resolve_opts.filterSerializedResponseHeaders, !!state.prerendering)
serialize_data(
item,
resolve_opts.filterSerializedResponseHeaders,
!!(state.prerendering || state.prerender_default === true)
)
)
.join('\n\t\t\t')}`;
}
Expand All @@ -363,7 +367,8 @@ export async function render_response({
// import the env.js module so that it evaluates before any user code can evaluate.
// TODO revert to using top-level await once https://bugs.webkit.org/show_bug.cgi?id=242740 is fixed
// https://github.com/sveltejs/kit/pull/11601
const load_env_eagerly = client.uses_env_dynamic_public && !!state.prerendering;
const load_env_eagerly =
client.uses_env_dynamic_public && (state.prerendering || state.prerender_default === true);

if (load_env_eagerly) {
modulepreloads.add(`${paths.app_dir}/env.js`);
Expand Down Expand Up @@ -568,14 +573,14 @@ export async function render_response({
'content-type': 'text/html'
});

if (state.prerendering) {
if (state.prerendering || state.prerender_default === true) {
// TODO read headers set with setHeaders and convert into http-equiv where possible
const csp_headers = csp.csp_provider.get_meta();
if (csp_headers) {
head.add_http_equiv(csp_headers);
}

if (state.prerendering.cache) {
if (state.prerendering?.cache) {
head.add_http_equiv(
`<meta http-equiv="cache-control" content="${state.prerendering.cache}">`
);
Expand Down
2 changes: 1 addition & 1 deletion packages/kit/src/runtime/server/respond.js
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ export async function internal_respond(request, options, manifest, state) {
prerender = node.prerender ?? prerender;
} else if (page_nodes) {
config = page_nodes.get_config() ?? config;
prerender = page_nodes.prerender();
prerender = state.prerender_default = page_nodes.prerender();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there any value in doing something like this immediately after this block, rather than adding prerender_default?

if (DEV && prerender) {
  state.prerendering = {
    dependencies: new Map(),
    remote_responses: new Map()
  };
}

It's a bit of a kludge but it would mean we didn't need to worry about forgetting the

|| state.prerender_default === true

part of

if (state.prerendering || state.prerender_default === true) {...}

Or does that not work for whatever reason?

@Nic-Polumeyv Nic-Polumeyv Jul 25, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked. event_state is created earlier in respond with prerendering: state.prerendering, so the request store would keep undefined unless that becomes a getter (or gets the same assignment). #16508 makes it a bit less cumbersome.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's true. But I think that itself is a problem — we have SSRState and EventState that overlap on prerender, and many functions that take both as arguments.

This was never intentional, just an artifact of organic software development — they should probably be unified into a single object. There are lots of adjacent tidy-ups that would be beneficial. But those would all be slightly disruptive changes that we should hold off on until the PR queue is emptier (specifically of things like #16464 and #15574), and as such I think we should hit pause on both this and #16508.

}

if (state.emulator?.platform) {
Expand Down
1 change: 1 addition & 0 deletions packages/kit/src/types/internal.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ export interface PrerenderDependency {
body: null | string | Uint8Array;
}

/** Internal context for the prerendering process */
export interface PrerenderOptions {
cache?: string; // including this here is a bit of a hack, but it makes it easy to add <meta http-equiv>
fallback?: boolean;
Expand Down
Loading