Skip to content

Static rendering all the routes, even the parameterized ones #1533

@johnnysprinkles

Description

@johnnysprinkles

This 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.

Screen Shot 2021-05-23 at 4 45 19 PM

https://github.com/johnnysprinkles/static-all-the-things/commit/b06f612a31b4009e5a44a4b05b97f7c7cffd37f1

This example just gives you a 404 for the parameterized routes.

First pass

https://github.com/johnnysprinkles/static-all-the-things/commit/3be17a2a84147cdc516f83c756fd72ebe021ce65

  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:

https://github.com/johnnysprinkles/static-all-the-things/commit/5ed2e708ec1a355788b8bfb9deb24ff70d51b8e2

  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:

https://github.com/johnnysprinkles/static-all-the-things/commit/22e3e98149876364d8a4317e9557f727e2287021

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions