loading...
Cover image for Svelte Prerendering

Svelte Prerendering

gobeli profile image Etienne Updated on ・2 min read

tldr (repo)

https://github.com/gobeli/svelte-prerender

Why Prerendering?

Prerendering is a process in which an application (usually a SPA or otherwise statically generated site) is rendered as HTML during build time. The static HTML is then shipped to the user along with JavaScript to "hydrate" the HTML. Hydrate meaning attaching itself to the DOM elements which are already there.

This is done in production builds, mainly for SEO and performance purposes.

Prerendering with Svelte

The svelte compiler is able to output code which can be used for ssr (server side rendering), the same output is useful for prerendering. Instead of creating a web server and using the ssr output there, we can create a script to evaluate the ssr code to HTML during build-time. This script can look something like this:

import { existsSync, promises as fs } from 'fs'
import { join } from 'path'

import App from '../src/app/App.svelte'

async function main() {
  const templatePath = join(process.cwd(), 'src', 'index.template')
  const publicPath = join(process.cwd(), 'public')

  const template = await fs.readFile(templatePath)
  const app = App.render()

  if (!existsSync(publicPath)) {
    await fs.mkdir(publicPath)
  }

  await fs.writeFile(
    join(publicPath, 'index.html'),
    template.toString().replace('%svelte.head%', app.head).replace('%svelte.html%', app.html)
  )
}

main()

A few things are still required for this to work:

  • A bundler to ensure we can import .svelte files in our node script
  • A HTML template to render the App into

Bundling the prerender-script

To bundle the prerender-script we are using rollup. We don't really need the output of the bundling process but would like to run the output immediately, that's why we are using the @rollup/plugin-run to execute the bundle.

import run from '@rollup/plugin-run'
import svelte from 'rollup-plugin-svelte'

export default {
  input: 'bin/prerender.js',
  output: {
    format: 'cjs',
    file: 'bin/dist/prerender.js',
  },
  plugins: [
    svelte({
      dev: false,
      generate: 'ssr',
      hydratable: true,
      css: function (css) {
        css.write('public/_dist_/main.css')
      },
    }),
    run(),
  ],
}

Notice, that we are using the svelte compiler option "generate" to ensure we generate ssr code.

HTML template

The HTML template is pretty simple, it provides a skeleton with placeholders for the prerendered app:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>A Prerendered Svelte App</title>
    <link rel="stylesheet" href="/_dist_/main.css" />
    %svelte.head%
  </head>
  <body>
    <div id="app">
      %svelte.html%
    </div>
    <script type="module" src="/_dist_/client.js"></script>
  </body>
</html>

Prerendered app

If we run the script a index.html file is created with the prerendered app:

...
    <div id="app">
      <div class="svelte-tzjjzo"><h1>A Svelte App</h1></div>
    </div>
...

Client side bundling

In this example the client side bundling is done with snowpack/parcel. It is important to use the hydratable svelte compiler option for the build of the client code, since we want the client code to hydrate the HTML and not overwrite it. Furthermore we can safely disable the css option, since we are writing the css out during prerendering:

["@snowpack/plugin-svelte", { "hydratable": true, "css": false }]

Photo by Daniel McCullough on Unsplash

Discussion

markdown guide