Skip to content
Draft
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
59 changes: 32 additions & 27 deletions packages/adapter-static/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import path from 'node:path';
import { platforms } from './platforms.js';
import { get_options_message } from './utils.js';

/** @type {import('./index.js').default} */
export default function (options) {
Expand All @@ -17,36 +18,13 @@ export default function (options) {
${dynamic_routes.map((route) => ` - ${path.posix.join(prefix, route.id)}`).join('\n')}\n`
);

const options = [
'set the `fallback` option — see https://svelte.dev/docs/kit/single-page-apps#usage for more info.',
'add `export const prerender = true` to your root `+layout.js/.ts` or `+layout.server.js/.ts` file. This will try to prerender all pages.',
'add `export const prerender = true` to any `+server.js/ts` files that are not fetched by page `load` functions.'
];

if (
has_param_routes ||
JSON.stringify(builder.config.kit.prerender.entries) !== '["*"]'
) {
let option = 'adjust the `prerender.entries` config option';
if (has_param_routes)
option += ' (routes with parameters are not part of entry points by default)';
options.push(option);
}

options.push(
"pass `strict: false` to `adapter-static` to ignore this error. Only do this if you are sure you don't need the routes in question in your final app, as they will be unavailable. See https://github.com/sveltejs/kit/tree/main/packages/adapter-static#strict for more info."
);

builder.log(
`You have the following options:${options.map((o) => `\n - ${o}`).join('')}\n`
get_options_message(
has_param_routes,
JSON.stringify(builder.config.kit.prerender.entries) !== '["*"]'
)
);

builder.log(
`If this doesn't help, you may need to use a different adapter. @sveltejs/adapter-static can only be used for sites that don't need a server for dynamic rendering, and can run on just a static file server.\n`
);

builder.log(`See https://svelte.dev/docs/kit/page-options#prerender for more details\n`);

const error = new Error('Encountered dynamic routes');
error.stack = '';
throw error;
Expand Down Expand Up @@ -99,6 +77,33 @@ ${dynamic_routes.map((route) => ` - ${path.posix.join(prefix, route.id)}`).join
}

if (!options) platform?.done(builder);
},
vite: {
plugins: [
{
name: 'vite-plugin-sveltekit-adapter-static',
configEnvironment(name) {
if (name === 'ssr') {
return {
define: {
__SVELTEKIT_ADAPTER_STATIC_FALLBACK__: options?.fallback ? 'true' : 'false'
}
};
}
},
applyToEnvironment(environment) {
return environment.name === 'ssr';
},
resolveId: {
filter: {
id: /^sveltekit:server-entry$/
},
handler() {
return this.resolve(import.meta.resolve('./src/dev.js'));
}
}
}
]
}
};
}
3 changes: 2 additions & 1 deletion packages/adapter-static/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
"files": [
"index.js",
"index.d.ts",
"platforms.js"
"platforms.js",
"src/**/*.js"
],
"scripts": {
"lint": "prettier --check .",
Expand Down
55 changes: 55 additions & 0 deletions packages/adapter-static/src/dev.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { env } from 'sveltekit:env';
import { Server } from 'sveltekit:server';
import { manifest } from 'sveltekit:server-manifest';
import { createReadableStream } from '@sveltejs/kit/node';
import { styleText } from 'node:util';
import { get_options_message } from '../utils.js';
import { set_prerendering } from '$app/env/internal';

set_prerendering(true);

/** @type {import('../../kit/src/types/internal.js').InternalServer} */
const server = new Server(manifest);

await server.init({
env,
read: (file) => createReadableStream(file)
});

/**
* @param {Request} request
* @returns {Promise<Response>}
*/
export async function fetch(request) {
return await server.respond(request, {
// simulate prerendering during development to surface errors earlier
prerendering: {
dependencies: new Map(),
remote_responses: new Map(),
fallback: __SVELTEKIT_ADAPTER_STATIC_FALLBACK__
},
Comment on lines +25 to +30

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

There should be a public and easier way to do this

getClientAddress() {
throw new Error('Cannot read clientAddress on prerendered pages');
},
// @ts-expect-error this is only needed during actual prerendering
read: undefined,
before_handle: async (event, _config, prerender, handle) => {
if (!event.isSubRequest && !event.isDataRequest && !event.isRemoteRequest && !prerender) {
const error = new Error('Encountered dynamic routes');
error.stack = '';

console.error(
styleText(
['bold', 'red'],
`@sveltejs/adapter-static: all routes must be fully prerenderable, but the ${event.url.pathname} route is dynamic\n`
)
);

console.log(get_options_message(!!Object.keys(event.params).length, false));

throw error;
}
return await handle();
}
});
Comment on lines +36 to +54

@teemingc teemingc Jul 24, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Need something public that lets us log errors when a page option isn't set

}
5 changes: 5 additions & 0 deletions packages/adapter-static/src/global-private.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
declare global {
export const __SVELTEKIT_ADAPTER_STATIC_FALLBACK__: boolean;
}

export {};
4 changes: 4 additions & 0 deletions packages/adapter-static/test/apps/prerendered/jsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "$app/tsconfig",
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const prerender = false;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<p>not prerendered</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<script>
import { not_allowed } from './remote';
const str = $derived(await not_allowed());
</script>

<p>{str}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { query } from '$app/server';

export const not_allowed = query(() => {
return 'foo';
});
4 changes: 4 additions & 0 deletions packages/adapter-static/test/apps/prerendered/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ test('exposes public env vars to the client', async ({ page }) => {
expect(await page.textContent('h1')).toEqual('The answer is 42');
expect(await page.textContent('h2')).toEqual('The dynamic answer is 42');
});

test.describe('dev', () => {
// TODO: add tests for prerender errors during development
});
10 changes: 9 additions & 1 deletion packages/adapter-static/test/apps/prerendered/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,15 @@ const config = {
},
plugins: [
sveltekit({
adapter: adapter()
adapter: adapter(),
experimental: {
remoteFunctions: true
},
compilerOptions: {
experimental: {
async: true
}
}
})
]
};
Expand Down
2 changes: 1 addition & 1 deletion packages/adapter-static/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
},
"types": ["node"]
},
"include": ["index.js", "test/utils.js"]
"include": ["index.js", "utils.js", "src/*.js", "src/*.ts", "test/utils.js"]
}
31 changes: 31 additions & 0 deletions packages/adapter-static/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Returns a message with a list of options to correctly configure the app.
* @param {boolean} has_param_routes
* @param {boolean} has_custom_entries
* @returns {string}
*/
export function get_options_message(has_param_routes, has_custom_entries) {
const options = [
'add `export const prerender = true` to your root `+layout.js/.ts` or `+layout.server.js/.ts` file. This will try to prerender all pages.',
'add `export const prerender = true` to any `+server.js/ts` files that are not fetched by page `load` functions.'
];

if (has_param_routes || has_custom_entries) {
let option = 'adjust the `prerender.entries` config option';
if (has_param_routes)
option += ' (routes with parameters are not part of entry points by default)';
options.push(option);
}

options.push(
'set the `fallback` option — see https://svelte.dev/docs/kit/single-page-apps#usage for more info.',
"pass `strict: false` to `adapter-static` to ignore this error. Only do this if you are sure you don't need the routes in question in your final app, as they will be unavailable. See https://github.com/sveltejs/kit/tree/main/packages/adapter-static#strict for more info."
);

let message = `You have the following options:${options.map((o) => `\n - ${o}`).join('')}`;
message +=
"\n\nIf this doesn't help, you may need to use a different adapter. @sveltejs/adapter-static can only be used for sites that don't need a server for dynamic rendering, and can run on just a static file server.";
message += '\n\nSee https://svelte.dev/docs/kit/page-options#prerender for more details\n';

return message;
}
7 changes: 4 additions & 3 deletions packages/kit/src/runtime/server/respond.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export async function internal_respond(request, options, manifest, state) {
/** @type {boolean[] | undefined} */
let invalidated_data_nodes;

/** Skipped for query remote function requests */
let skip_route_resolution = false;

if (is_route_resolution_request) {
Expand Down Expand Up @@ -368,7 +369,7 @@ export async function internal_respond(request, options, manifest, state) {
return text('Not found', { status: 404, headers });
}

if (!state.prerendering?.fallback && !skip_route_resolution) {
if ((!state.prerendering?.fallback && !skip_route_resolution) || is_data_request) {
try {
const matchers = await manifest._.matchers();
const result = find_route(resolved_path, manifest._.routes, matchers);
Expand Down Expand Up @@ -435,7 +436,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();
}

if (state.emulator?.platform) {
Expand Down Expand Up @@ -614,7 +615,7 @@ export async function internal_respond(request, options, manifest, state) {
});
}

if (options.hash_routing || state.prerendering?.fallback) {
if ((options.hash_routing || state.prerendering?.fallback) && !is_data_request) {
return await render_response({
event,
event_state,
Expand Down
Loading