Solving SvelteKit slug route reload mess

As this site is built on top of SvelteKit and Mdsvex I today ran into two problems:

  • How to render dynamically loaded svx files on server side, to avoid shipping the full posts library to the client side, and
  • How to make my links between two slugs work, when the slug is handled by the same route

As a reminder (perhaps to myself most of all) this site is built from ground up with one of the motivations being to learn SEO. And to have a place where I ramble freely.

How to render dynamically imported svx files on server side

The main problem was that using +page.js to load these posts meant that my whole “posts library” would get imported in the code sent to the client; and that would also happen on every request to a single blog post.

In general, I wanted to keep all the post loading and markdown rendering logic on server side. Why? Because instead of the file based “database” of posts, I will probably later on swap it for something more complex that pulls md from a database. I might also want to include dynamic data on some pages. And I also want to have server side rendering and caching for SEO.

  • I wanted to do the rendering on server side, but still be able to use javascript on client side so csr=false was out of question.

  • Renaming +page.js to +page.server.js excluded my posts library from getting sent to the client, but came with another problem. When you import a component it will contain a render function to render it. But because functions cannot be serialized (to be passed from server to the client) this didn’t work right away with the existing setup.

For reference, I initially used <svelte:component this={component} /> for mounting the blog posts. If you’re interested in how the posts were loaded, see my first post

Anyway, here’s how to render a component on server side and then “mount” it on client side:

In src/routes/[slug]/+page.server.js:

export const load = async () => {
  ...
  const rendered = post.component.render()
  return {
    post: {
      title: post.title,
      component: {
        html: rendered.html,
        css: rendered.css
      }
    }
  }
}

And in src/routes/[slug]/+page.svelte:

{@html post.component.html}
{@html "<style>" + post.component.css.code + "</style>"}

Note however that this did NOT work in the svx files anymore:

li {
  @apply bg-pink-500;
}

But this does:

li {
  background-color: red;
}

Including client side javascript in the post svx files (inside script tags) also doesn’t work with this approach. But that’s fine, for me, for now.

Since the eventual goal is to migrate everything to a database, I probably would not want to be pulling js from there anyway. But time will tell.

And for the pages where I do want to have client side logic — I can just create separate routes for them. For now.

How to make a slug route page reload when linking from one slug to another

But then I also had another problem which had to do with linking from one slug to another. Since the route stays the same, the page would not reload.

The recommended method to solve this seemed to be to add either rel=“external” or target=“_self” to your links, to make the page actually reload. This might work for you.

But I did not really want to start figuring out how to do that in my svx (so basically markdown) files; and I still wanted to stick to the markdown format for specifying links. Instead of writing the links as html in the markdown — which would have worked of course.

But the solution was easy enough. And it involved reloading not the page, but the content on the page.

Instead of this is +page.svelte:

const post = data.post

Do this:

let post
$: post = data.post

$: will cause the code to be re-evaluated when data changes. Data comes from the load function in +page.server.js which seemed to reload every time the slug changed; but that did not by default reflect on the frontend.

So now, for example these links here should work:

All three routes (sveltekit-page-reload, seo-resources, and market-research-tools) are handled by the same route handler in src/routes/[slug]