Static rendering all the routes, even the parameterized ones
See original GitHub issueThis is my exploration into the idea of static rendering all the routes such that they can be served in a totally language agnostic way. This is of course easy to do already for routes with no route params, but not so simple for the parameterized routes.
Why would I want to pre-render a parameterized route, where all the data comes from the clientside XHR call? Aren’t I just prerendering a blank page? Not at all my friend, you prerender as much as you can which would typically include at least the global nav bar. Having that appear instantly instead of merely quickly upon hydration makes all the difference in giving your site an app-like feel.
And in my case it’s especially important since I’m not using client-side routing, so each page load needs to be as fast-feeling as possible. Why no client-side routing? Imagine a large site with 1000 pages, an in-house project spanning many years and having many separate teams. Building this as a single app is bound to result in slow build times, so you can split it up into 10 Svelte projects where each has 100 pages (all behind a reverse proxy). But as long as you’re splitting it at all it makes sense to just split it completely with full page reloads on every transition. Now you can reproportion ownership fluidly as you see fit, and most importantly, you can iteratively migrate page by page to some new framework. Perhaps whatever the hot framework of 2025 is. This a whole separate topic I could say a lot about but let’s leave it at that.
So anyway, the '*'
you pass to kit.prerender.pages
only does the parameterless routes. A first attempt at this might be to add another route in there that has garbage for the param since it won’t even be looked at from the serverside, and updating your code that runs onMount
to only look at window.location
instead of the page store.
I put together a repo to try out some ideas, at https://github.com/johnnysprinkles/static-all-the-things
Setup
The first commit is just setting things up with a small constellation of services to make a more realistic prod-like setup. There’s SvelteKit, there’s a simple API server, and there’s a web server that serves the adapter-static compiled assets off of disk. Just to ensure we have a complete break between build time and runtime the web server is written in Python. Could be anything (for me it’ll probably be Java or Kotlin), but all the matters is it’s not Node.
On the SvelteKit side I added two routes related to airplanes, one is a parameterless List page, and one is a detail page that has an [id]
in its route.
This example just gives you a 404 for the parameterized routes.
First pass
pages: [
'*',
'/airplane/XXX',
],
This manually adds preprender paths with garbage for the parameters, and it does work. I can run it and my dynamic pages function. It’s all hacked up and manual though, we can do better.
First attempt at interpolating
We’d really like to keep using the store, which should be a source of truth for route params and other request-specific data, instead of going around it to inspect the window.location
. So instead of passing garbage for the params when server-side rendering, what about if we pass something meaningful? Such as just the param name in square brackets? (I know, this is kind of overloading the meaning of square brackets, but it’s just a start).
If we do that, we can just have the web server replace e.g. [id]
with the actual ID. A hacked up and manual version of this is in:
pages: [
'*',
'/airplane/[id]',
],
More automated way of interpolating
We can tidy things up a bit by taking advantage of the manifest.js file, which knows both the names of all the route params (in JS comments) and the regexes that match, e.g.
export const routes = [
// src/routes/index.svelte
[/^\/$/, [c[0], c[2]], [c[1]]],
// src/routes/networking/addresses/list.svelte
[/^\/networking\/addresses\/list\/?$/, [c[0], c[3]], [c[1]]],
...
So the next version of this reads and parses that:
It would be nice if we had routes presented in a language agnostic way, such as a routes.json
file that lives next to the manifest. A possible feature request.
Once we have that we can fill in the page store via regex slice and dice. This would be a little easier if the page store data was pulled out of that structure (and also perhaps surrounded by special comment tokens). So for example we currently get this:
<script type="module">
import { start } from "/./_app/start-aec04e6a.js";
start({
target: document.querySelector("#svelte"),
paths: {"base":"","assets":"/."},
session: {},
host: location.host,
route: false,
spa: false,
trailing_slash: "never",
hydrate: {
status: 200,
error: null,
nodes: [
import("/./_app/pages/__layout.svelte-1240a5ff.js"),
import("/./_app/pages/airplane/[id].svelte-a58c7efe.js")
],
page: {
host: location.host, // TODO this is redundant
path: "/airplane/[id]",
query: new URLSearchParams(""),
params: {"id":"[id]"}
}
}
});
</script>
But if it were more like this it would be easier for any language that can emit JSON to fill it in:
<script type="module">
let pageStoreData = /** PAGE_STORE_START **/ {
host: location.host,
path: "/airplane/[id]",
queryString: "",
params: {"id":"[id]"}
}/** PAGE_STORE_END **/;
import { start } from "/./_app/start-aec04e6a.js";
start({
target: document.querySelector("#svelte"),
paths: {"base":"","assets":"/."},
session: {},
host: location.host,
route: false,
spa: false,
trailing_slash: "never",
hydrate: {
status: 200,
error: null,
nodes: [
import("/./_app/pages/__layout.svelte-1240a5ff.js"),
import("/./_app/pages/airplane/[id].svelte-a58c7efe.js")
],
page: {
host: pageStoreData.host,
path: pageStoreData.path,
query: new URLSearchParams(pageStoreData.queryString),
params: pageStoreData.params
}
}
});
</script>
So that’s where things are at now, this will be an ongoing exploration.
Issue Analytics
- State:
- Created 2 years ago
- Comments:39 (27 by maintainers)
Top GitHub Comments
To be honest, I expected
crawl: true
in the config to behave, ascrawlAllowDynamicRoutes
. Since it crawls in order to build the static site, it is to be expected that all links that are encountered are rendered.If someone doesn’t want to pre-render the entire database, one should set the pages to be rendered in the
config.prerender.entries
. It’s simpler to config a couple of pages that need to be rebuilt (or need to be built for the first time), than to list the whole content of a database (which may be thousands of pages).These are my two cents from reading the documentation, as I can’t find this problem mentioned anywhere in the docs.
It’s not clear to me what you’re requesting. Do you have a concise way of stating it? Parameterized routes are currently prerendered, but must be either linked to or specified as an option