Menu

Introduction edit this section

Note: Sapper is in early development, and some things may change before we hit version 1.

What is Sapper?

Sapper is a framework for building extremely high-performance web apps. You're looking at one right now! There are two basic concepts:

  • Each page of your app is a Svelte component
  • You create pages by adding files to the src/routes directory of your project. These will be server-rendered so that a user's first visit to your app is as fast as possible, then a client-side app takes over

Building an app with all the modern best practices — code-splitting, offline support, server-rendered views with client-side hydration — is fiendishly complicated. Sapper does all the boring stuff for you so that you can get on with the creative part.

You don't need to know Svelte to understand the rest of this guide, but it will help. In short, it's a UI framework that compiles your components to highly optimized vanilla JavaScript. Read the introductory blog post and the guide to learn more.

Why the name?

In war, the soldiers who build bridges, repair roads, clear minefields and conduct demolitions — all under combat conditions — are known as sappers.

For web developers, the stakes are generally lower than for combat engineers. But we face our own hostile environment: underpowered devices, poor network connections, and the complexity inherent in front-end engineering. Sapper, which is short for Svelte app maker, is your courageous and dutiful ally.

Comparison with Next.js

Next.js is a React framework from Zeit, and is the inspiration for Sapper. There are a few notable differences, however:

  • Sapper is powered by Svelte instead of React, so it's faster and your apps are smaller
  • Instead of route masking, we encode route parameters in filenames (see the routing section below)
  • As well as pages, you can create server routes in your src/routes directory. This makes it very easy to, for example, add a JSON API such as the one powering this very page (try visiting /guide.json)
  • Links are just <a> elements, rather than framework-specific <Link> components. That means, for example, that this link right here, despite being inside a blob of markdown, works with the router as you'd expect

Getting started

The easiest way to start building a Sapper app is to clone the sapper-template repo with degit:

npx degit sveltejs/sapper-template#rollup my-app
# or: npx degit sveltejs/sapper-template#webpack my-app
cd my-app
npm install
npm run dev

This will scaffold a new project in the my-app directory, install its dependencies, and start a server on localhost:3000. Try editing the files to get a feel for how everything works – you may not need to bother reading the rest of this guide!

Sapper app structure edit this section

This section is a reference for the curious. We recommend you play around with the project template first, and come back here when you've got a feel for how things fit together.

If you take a look inside the sapper-template repo, you'll see some files that Sapper expects to find:

├ package.json
├ src
│ ├ routes
│ │ ├ # your routes here
│ │ ├ _error.html
│ │ └ index.html
│ ├ client.js
│ ├ server.js
│ ├ service-worker.js
│ └ template.html
├ static
│ ├ # your files here
└ rollup.config.js / webpack.config.js

When you first run Sapper, it will create an additional __sapper__ directory containing generated files.

You'll notice a few extra files and a cypress directory which relates to testing — we don't need to worry about those right now.

You can create these files from scratch, but it's much better to use the template. See getting started for instructions on how to easily clone it

package.json

Your package.json contains your app's dependencies and defines a number of scripts:

  • npm run dev — start the app in development mode, and watch source files for changes
  • npm run build — build the app in production mode
  • npm run export — bake out a static version, if applicable (see exporting)
  • npm start — start the app in production mode after you've built it
  • npm test — run the tests (see testing)

src

This contains the three entry points for your app — src/client.js, src/server.js and (optionally) src/service-worker.js — along with a src/template.html file.

src/client.js

This must import, and call, the start function from the generated __sapper__/client.js file:

import * as sapper from '../__sapper__/client.js';

sapper.start({
  target: document.querySelector('#sapper')
});

In many cases, that's the entirety of your entry module, though you can do as much or as little here as you wish. See the client API section for more information on functions you can import.

src/server.js

This is a normal Express (or Polka, etc) app, with three requirements:

  • it should serve the contents of the static folder, using for example sirv
  • it should call app.use(sapper.middleware()) at the end, where sapper is imported from ../__sapper__/server.js
  • it must listen on process.env.PORT

Beyond that, you can write the server however you like.

src/service-worker.js

Service workers act as proxy servers that give you fine-grained control over how to respond to network requests. For example, when the browser requests /goats.jpg, the service worker can respond with a file it previously cached, or it can pass the request on to the server, or it could even respond with something completely different, such as a picture of llamas.

Among other things, this makes it possible to build applications that work offline.

Because every app needs a slightly different service worker (sometimes it's appropriate to always serve from the cache, sometimes that should only be a last resort in case of no connectivity), Sapper doesn't attempt to control the service worker. Instead, you write the logic in service-worker.js. You can import any of the following from ../__sapper__/service-worker.js:

  • files — an array of files found in the static directory
  • shell — the client-side JavaScript generated by the bundler (Rollup or webpack)
  • routes — an array of { pattern: RegExp } objects you can use to determine whether a Sapper-controlled page is being requested
  • timestamp — the time the service worker was generated (useful for generating unique cache names)

src/template.html

This file is a template for responses from the server. Sapper will inject content that replaces the following tags:

  • %sapper.base% — a <base> element (see base URLs)
  • %sapper.styles% — critical CSS for the page being requested
  • %sapper.head% — HTML representing page-specific <head> contents, like <title>
  • %sapper.html% — HTML representing the body of the page being rendered
  • %sapper.scripts% — script tags for the client-side app

src/routes

This is the meat of your app — the pages and server routes. See the section on routing for the juicy details.

static

This is a place to put any files that your app uses — fonts, images and so on. For example static/favicon.png will be served as /favicon.png.

Sapper doesn't serve these files — you'd typically use sirv or serve-static for that — but it will read the contents of the static folder so that you can easily generate a cache manifest for offline support (see service-worker.js).

rollup.config.js / webpack.config.js

Sapper can use Rollup or webpack to bundle your app. You probably won't need to change the config, but if you want to (for example to add new loaders or plugins), you can.

Routing edit this section

As we've seen, there are two types of route in Sapper — pages, and server routes.

Pages

Pages are Svelte components written in .html files. When a user first visits the application, they will be served a server-rendered version of the route in question, plus some JavaScript that 'hydrates' the page and initialises a client-side router. From that point forward, navigating to other pages is handled entirely on the client for a fast, app-like feel.

The filename determines the route. For example, src/routes/index.html is the root of your site:

<!-- src/routes/index.html -->
<svelte:head>
  <title>Welcome</title>
</svelte:head>

<h1>Hello and welcome to my site!</h1>

A file called either src/routes/about.html or src/routes/about/index.html would correspond to the /about route:

<!-- src/routes/about.html -->
<svelte:head>
  <title>About</title>
</svelte:head>

<h1>About this site</h1>
<p>TODO...</p>

Dynamic parameters are encoded using [brackets]. For example, here's how you could create a page that renders a blog post:

<!-- src/routes/blog/[slug].html -->
<svelte:head>
  <title>{article.title}</title>
</svelte:head>

<h1>{article.title}</h1>

<div class='content'>
  {@html article.html}
</div>

<script>
  export default {
    // the (optional) preload function takes a
    // `{ params, query }` object and turns it into
    // the data we need to render the page
    preload({ params, query }) {
      // the `slug` parameter is available because this file
      // is called [slug].html
      const { slug } = params;

      return this.fetch(`blog/${slug}.json`).then(r => r.json()).then(article => {
        return { article };
      });
    }
  };
</script>

See the section on preloading for more info about preload and this.fetch

Server routes

Server routes are modules written in .js files that export functions corresponding to HTTP methods. Each function receives HTTP request and response objects as arguments, plus a next function. This is useful for creating a JSON API. For example, here's how you could create an endpoint that served the blog page above:

// routes/blog/[slug].json.js
import db from './_database.js'; // the underscore tells Sapper this isn't a route

export async function get(req, res, next) {
  // the `slug` parameter is available because this file
  // is called [slug].json.js
  const { slug } = req.params;

  const article = await db.get(slug);

  if (article !== null) {
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify(article));
  } else {
    next();
  }
}

delete is a reserved word in JavaScript. To handle DELETE requests, export a function called del instead.

File naming rules

There are three simple rules for naming the files that define your routes:

  • A file called src/routes/about.html corresponds to the /about route. A file called src/routes/blog/[slug].html corresponds to the /blog/:slug route, in which case params.slug is available to preload
  • The file src/routes/index.html corresponds to the root of your app. src/routes/about/index.html is treated the same as src/routes/about.html.
  • Files and directories with a leading underscore do not create routes. This allows you to colocate helper modules and components with the routes that depend on them — for example you could have a file called src/routes/_helpers/datetime.js and it would not create a /_helpers/datetime route

Error page

In addition to regular pages, there is a 'special' page that Sapper expects to find — src/routes/_error.html. This will be shown when an error occurs while rendering a page.

The error object is made available to the template along with the HTTP status code.

Regexes in routes

You can use a subset of regular expressions to qualify route parameters, by placing them in parentheses after the parameter name.

For example, src/routes/items/[id([0-9]+)].html would only match numeric IDs — /items/123 would match, but /items/xyz would not.

Because of technical limitations, the following characters cannot be used: /, \, ?, :, ( and ).

Client API edit this section

The __sapper__/client.js module contains functions for controlling your app and responding to events.

start({ target, store? })

  • target — an element to render pages to
  • store — an function that, given some data, returns a Store object. See the state management section for more detail

This configures the router and starts the application — listens for clicks on <a> elements, interacts with the history API, and renders and updates your Svelte components.

Returns a Promise that resolves when the initial page has been hydrated.

import * as sapper from '../__sapper__/client.js';

sapper.start({
  target: document.querySelector('#sapper')
}).then(() => {
  console.log('client-side app has started');
});

goto(href, options?)

  • href — the page to go to
  • options — can include a replaceState property, which determines whether to use history.pushState (the default) or history.replaceState). Not required

Programmatically navigates to the given href. If the destination is a Sapper route, Sapper will handle the navigation, otherwise the page will be reloaded with the new href. (In other words, the behaviour is as though the user clicked on a link with this href.)

prefetch(href)

  • href — the page to prefetch

Programmatically prefetches the given page, which means a) ensuring that the code for the page is loaded, and b) calling the page's preload method with the appropriate options. This is the same behaviour that Sapper triggers when the user taps or mouses over an <a> element with rel=prefetch.

prefetchRoutes(routes?)

  • routes — an optional array of strings representing routes to prefetch

Programmatically prefetches the code for routes that haven't yet been fetched. Typically, you might call this after sapper.start() is complete, to speed up subsequent navigation (this is the 'L' of the PRPL pattern). Omitting arguments will cause all routes to be fetched, or you can specify routes by any matching pathname such as /about (to match src/routes/about.html) or /blog/* (to match src/routes/blog/[slug].html). Unlike prefetch, this won't call preload for individual pages.

Preloading edit this section

As seen in the routing section, top-level page components can have a preload function that will load some data that the page depends on. This is similar to getInitialProps in Next.js or asyncData in Nuxt.js.

<script>
  export default {
    preload({ params, query }) {
      const { slug } = params;

      return this.fetch(`blog/${slug}.json`).then(r => r.json()).then(article => {
        return { article };
      });
    }
  };
</script>

Your preload function is optional; whether or not you include it, the component will have access to the query and params objects, on top of any default data specified with a data property.

The top-level _layout.html component is rendered with a preloading value: true during preloading, false otherwise. This value is useful to display a loading spinner or otherwise indicate that a navigation is in progress.

<!-- src/routes/_layout.html -->
{#if preloading}
  <div>Loading...</div>
{/if}

<svelte:component this={child.component} {...child.props}/>

The preloading value is only set during page navigations. Prefetching (see below) does not set preloading since it is intended to be transparent to the user.

Argument

The preload function receives a { params, query } object where params is derived from the URL and the route filename, and query is an object of values in the query string.

So if the example above was src/routes/blog/[slug].html and the URL was /blog/some-post?foo=bar&baz, the following would be true:

  • params.slug === 'some-post'
  • query.foo === 'bar'
  • query.baz === true

Return value

If you return a Promise from preload, the page will delay rendering until the promise resolves. You can also return a plain object.

When Sapper renders a page on the server, it will attempt to serialize the resolved value (using devalue) and include it on the page, so that the client doesn't also need to call preload upon initialization. Serialization will fail if the value includes functions or custom classes (cyclical and repeated references are fine, as are built-ins like Date, Map, Set and RegExp).

Context

Inside preload, you have access to three methods...

  • this.fetch(url, options)
  • this.error(statusCode, error)
  • this.redirect(statusCode, location)

...and this.store, if you're using state management.

this.fetch

In browsers, you can use fetch to make AJAX requests, for getting data from your server routes (among other things). On the server it's a little trickier — you can make HTTP requests, but you must specify an origin, and you don't have access to cookies. This means that it's impossible to request data based on the user's session, such as data that requires you to be logged in.

To fix this, Sapper provides this.fetch, which works on the server as well as in the client:

<script>
  export default {
    preload() {
      return this.fetch(`secret-data.json`, {
        credentials: 'include'
      }).then(r => {
        // ...
      });
    }
  };
</script>

Note that you will need to use session middleware such as express-session in your app/server.js in order to maintain user sessions or do anything involving authentication.

this.error

If the user navigated to /blog/some-invalid-slug, we would want to render a 404 Not Found page. We can do that with this.error:

<script>
  export default {
    preload({ params, query }) {
      const { slug } = params;

      return this.fetch(`blog/${slug}.json`).then(r => {
        // assume all responses are either 200 or 404
        if (r.status === 200) {
          return r.json().then(article => {
            return { article };
          });
        } else {
          this.error(404, 'Not found');
        }
      });
    }
  };
</script>

The same applies to other error codes you might encounter.

this.redirect

You can abort rendering and redirect to a different location with this.redirect:

<script>
  export default {
    preload({ params, session }) {
      const { user } = this.store.get();

      if (!user) {
        return this.redirect(302, 'login');
      }

      return {
        user
      };
    }
  };
</script>

Layouts edit this section

So far, we've treated pages as entirely standalone components — upon navigation, the existing component will be destroyed, and a new one will take its place.

But in many apps, there are elements that should be visible on every page, such as top-level navigation or a footer. Instead of repeating them in every page, we can use layout components.

To create a layout component that applies to every page, make a file called src/routes/_layout.html. The default layout component (the one that Sapper uses if you don't bring your own) looks like this...

<svelte:component this={child.component} {...child.props}/>

...but we can add whatever markup, styles and behaviour we want. For example, let's add a nav bar:

<!-- src/routes/_layout.html -->
<nav>
  <a href=".">Home</a>
  <a href="about">About</a>
  <a href="settings">Settings</a>
</nav>

<svelte:component this={child.component} {...child.props}/>

Sapper computes the child property based on which page the user has navigated to. If we create pages for /, /about and /settings...

<!-- src/routes/index.html -->
<h1>Home</h1>
<!-- src/routes/about.html -->
<h1>About</h1>
<!-- src/routes/settings.html -->
<h1>Settings</h1>

...the nav will always be visible, and clicking between the three pages will only result in the <h1> being replaced.

Nested routes

Suppose we don't just have a single /settings page, but instead have nested pages like /settings/profile and /settings/notifications with a shared submenu (for an real-life example, see github.com/settings).

We can create a layout that only applies to pages below /settings (while inheriting the root layout with the top-level nav):

<!-- src/routes/settings/_layout.html -->
<h1>Settings</h1>

<div class="submenu">
  <a href="settings/profile">Profile</a>
  <a href="settings/notifications">Notifications</a>
</div>

<svelte:component this={child.component} {...child.props}/>

In addition to child.component and child.props, there is a child.segment property which is useful for things like styling:

<div class="submenu">
-	<a href="settings/profile">Profile</a>
-	<a href="settings/notifications">Notifications</a>
+	<a
+		class={child.segment === "profile" ? "selected" : ""}
+		href="settings/profile"
+	>Profile</a>
+
+	<a
+		class={child.segment === "notifications" ? "selected" : ""}
+		href="settings/notifications"
+	>Notifications</a>
</div>

Preloading

Like page components, layout components can use preload:

<!-- src/routes/foo/_layout.html -->
<svelte:component
  this={child.component}
  someData={thingAllChildComponentsWillNeed}
  {...child.props}
/>

<script>
  export default {
    async preload() {
      return {
        thingAllChildComponentsWillNeed: await loadSomeData()
      };
    }
  };
</script>

Server-side rendering edit this section

Sapper, by default, renders server-side first (SSR), and then re-mounts any dynamic elements on the client. Svelte provides excellent support for this. This has benefits in performance and search engine indexing, among others, but comes with its own set of complexities.

Making a component SSR compatible

Sapper works well with most third-party libraries you are likely to come across. However, sometimes, a third-party library comes bundled in a way which allows it to work with multiple different module loaders. Sometimes, this code creates a dependency on window, such as checking for the existence of window.global might do.

Since there is no window in a server-side environment like Sapper's, the action of simply importing such a module can cause the import to fail, and terminate the Sapper's server with an error such as:

ReferenceError: window is not defined

The way to get around this is to use a dynamic import for your component, from within the oncreate hook (which is only called on the client), so that your import code is never called on the server.

export default {
  async oncreate () {
    const MyComponent = await import('my-non-ssr-component')
    this.set({ MyComponent })
  }
}

You can then use your component within your app:

<svelte:component this={MyComponent}
  {prop1}
  prop2="foo"
/>

It might be the case that your component uses a default export, which actually ends up being an object with a property called default, in which case you would import it as { default: MyComponent } rather than just MyComponent

State management edit this section

Sapper integrates with the built-in Svelte store. If you're not familiar with Store, read the Svelte state management guide before continuing with this section.

To use Store, you must integrate it with your server and client apps separately.

On the server

Whereas the client-side app has a single Store instance that lasts as long as the page is open, the server-side app must create a new store for each request:

// app/server.js
import { Store } from 'svelte/store.js';

express() // or Polka, or a similar framework
  .use(
    compression({ threshold: 0 }),
    serve('assets'),
    authenticationMiddleware(),
    sapper.middleware({
      store: request => {
        return new Store({
          user: request.user
        });
      }
    })
  )
  .listen(process.env.PORT);

In this example, we're using some imaginary authenticationMiddleware that creates a request.user object based on the user's cookies. (In real life it tends to be a bit more involved — see express-session and Passport if you're ready to learn more about sessions and authentication.)

Because we've supplied a store option, Sapper creates a new Store instance for each new request. The data in our store will be used to render the HTML that Sapper responds with.

On the client

This time around, we're creating a single store that is attached to each page as the user navigates around the app.

import * as sapper from '../__sapper__/client.js';
import { Store } from 'svelte/store.js';

sapper.start({
  target: document.querySelector('#sapper'),
  store: data => {
    // `data` is whatever was in the server-side store
    return new Store(data);
  }
});

In order to re-use the server-side store data, it must be serializable (using devalue) — no functions or custom classes, just built-in JavaScript data types

Prefetching edit this section

Sapper uses code splitting to break your app into small chunks (one per route), ensuring fast startup times.

For dynamic routes, such as our src/routes/blog/[slug].html example, that's not enough. In order to render the blog post, we need to fetch the data for it, and we can't do that until we know what slug is. In the worst case, that could cause lag as the browser waits for the data to come back from the server.

rel=prefetch

We can mitigate that by prefetching the data. Adding a rel=prefetch attribute to a link...

<a rel=prefetch href='blog/what-is-sapper'>What is Sapper?</a>

...will cause Sapper to run the page's preload function as soon as the user hovers over the link (on a desktop) or touches it (on mobile), rather than waiting for the click event to trigger navigation. Typically, this buys us an extra couple of hundred milliseconds, which is the difference between a user interface that feels laggy, and one that feels snappy.

rel=prefetch is a Sapper idiom, not a standard attribute for <a> elements

Building edit this section

Up until now we've been using sapper dev to build our application and run a development server. But when it comes to production, we want to create a self-contained optimized build.

sapper build

This command packages up your application into the __sapper__/build directory. (You can change this to a custom directory, as well as controlling various other options — do sapper build --help for more information.)

The output is a Node app that you can run from the project root:

node __sapper__/build

Exporting edit this section

Many sites are effectively static, which is to say they don't actually need an Express server backing them. Instead, they can be hosted and served as static files, which allows them to be deployed to more hosting environments (such as Netlify or GitHub Pages). Static sites are generally cheaper to operate and have better performance characteristics.

Sapper allows you to export a static site with a single zero-config sapper export command. In fact, you're looking at an exported site right now!

Static doesn't mean non-interactive — your Svelte components work exactly as they do normally, and you still get all the benefits of client-side routing and prefetching.

sapper export

Inside your Sapper project, try this:

# npx allows you to use locally-installed dependencies
npx sapper export

This will create a __sapper__/export folder with a production-ready build of your site. You can launch it like so:

npx serve __sapper__/export

Navigate to localhost:5000 (or whatever port serve picked), and verify that your site works as expected.

You can also add a script to your package.json...

{
  "scripts": {
    ...
    "export": "sapper export"
  }
}

...allowing you to npm run export your app.

How it works

When you run sapper export, Sapper first builds a production version of your app, as though you had run sapper build, and copies the contents of your assets folder to the destination. It then starts the server, and navigates to the root of your app. From there, it follows any <a> elements it finds, and captures any data served by the app.

Because of this, any pages you want to be included in the exported site must be reachable by <a> elements. Additionally, any non-page routes should be requested in preload, not in oncreate or elsewhere.

When not to export

The basic rule is this: for an app to be exportable, any two users hitting the same page of your app must get the same content from the server. In other words, any app that involves user sessions or authentication is not a candidate for sapper export.

Note that you can still export apps with dynamic routes, like our src/routes/blog/[slug].html example from earlier. sapper export will intercept fetch requests made inside preload, so the data served from src/routes/blog/[slug].json.js will also be captured.

Route conflicts

Because sapper export writes to the filesystem, it isn't possible to have two server routes that would cause a directory and a file to have the same name. For example, src/routes/foo/index.js and src/routes/foo/bar.js would try to create export/foo and export/foo/bar, which is impossible.

The solution is to rename one of the routes to avoid conflict — for example, src/routes/foo-bar.js. (Note that you would also need to update any code that fetches data from /foo/bar to reference /foo-bar instead.)

For pages, we skirt around this problem by writing export/foo/index.html instead of export/foo.

Deployment edit this section

Sapper apps run anywhere that supports Node 8 or higher.

Deploying to Now

We can very easily deploy our apps to Now:

npm install -g now
now

This will upload the source code to Now, whereupon it will do npm run build and npm start and give you a URL for the deployed app.

For other hosting environments, you may need to do npm run build yourself.

Deploying service workers

Sapper makes the Service Worker file (service-worker.js) unique by including a timestamp in the source code (calculated using Date.now()).

In environments where the app is deployed to multiple servers (such as Now), it is advisable to use a consistent timestamp for all deployments. Otherwise, users may run into issues where the Service Worker updates unexpectedly because the app hits server 1, then server 2, and they have slightly different timestamps.

To override Sapper's timestamp, you can use an environment variable (e.g. SAPPER_TIMESTAMP) and then modify the service-worker.js:

const timestamp = process.env.SAPPER_TIMESTAMP; // instead of `import { timestamp }`

const ASSETS = `cache${timestamp}`;

export default {
  /* ... */
  plugins: [
    /* ... */
    replace({
      /* ... */
      'process.env.SAPPER_TIMESTAMP': process.env.SAPPER_TIMESTAMP || Date.now()
    })
  ]
}

Then you can set it using the environment variable, e.g.:

SAPPER_TIMESTAMP=$(date +%s%3N) npm run build

When deploying to Now, you can pass the environment variable into Now itself:

now -e SAPPER_TIMESTAMP=$(date +%s%3N)

Security edit this section

By default, Sapper does not add security headers to your app, but you may add them yourself using middleware such as Helmet.

Content Security Policy (CSP)

Sapper generates inline <script>s, which can fail to execute if Content Security Policy (CSP) headers disallow arbitrary script execution (unsafe-inline).

To work around this, Sapper can inject a nonce which can be configured with middleware to emit the proper CSP headers. Here is an example using Express and Helmet:

// server.js
import uuidv4 from 'uuid/v4';
import helmet from 'helmet';

app.use((req, res, next) => {
  res.locals.nonce = uuidv4();
  next();
});
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      scriptSrc: [
        "'self'",
        (req, res) => `'nonce-${res.locals.nonce}'`
      ]
    }
  }
}));
app.use(sapper.middleware());

Using res.locals.nonce in this way follows the convention set by Helmet's CSP docs.

Base URLs edit this section

Ordinarily, the root of your Sapper app is served at /. But in some cases, your app may need to be served from a different base path — for example, if Sapper only controls part of your domain, or if you have multiple Sapper apps living side-by-side.

This can be done like so:

// app/server.js

express() // or Polka, or a similar framework
  .use(
    '/my-base-path', // <!-- add this line
    compression({ threshold: 0 }),
    serve('assets'),
    sapper.middleware()
  )
  .listen(process.env.PORT);

Sapper will detect the base path and configure both the server-side and client-side routers accordingly.

If you're exporting your app, you will need to tell the exporter where to begin crawling:

sapper export --basepath my-base-path

Testing edit this section

You can use whatever testing frameworks and libraries you'd like. The default in sapper-template is Cypress.

Running the tests

npm test

This will start the server and open Cypress. You can (and should!) add your own tests in cypress/integration/spec.js — consult the docs for more information.

Debugging edit this section

Debugging your server code is particularly easy with ndb. Install it globally...

npm install -g ndb

...then run Sapper:

ndb npm run dev

This assumes that npm run dev runs sapper dev. You can also run Sapper via npx, as in ndb npx sapper dev.

Note that you may not see any terminal output for a few seconds while ndb starts up.

Migrating edit this section

Until we reach version 1.0, there may be occasional changes to the project structure Sapper expects. If you upgrade and Sapper sends you here, it's because it was unable to fix your project automatically.

0.6 to 0.7

Consult sapper-template for full examples of all the below points.

package.json

To start a dev server, use sapper dev rather than node server.js. In all likelihood, your package.json will have an npm run dev script that will need to be updated.

Entry points

As of version 0.7, Sapper expects to find your entry points — for client, server and service worker — in an app folder. Instead of using magically-injected __variables__, each entry point imports from its corresponding file in the app/manifests folder. These are automatically generated by Sapper.

// app/client.js (formerly templates/main.js)
import { init } from 'sapper/runtime.js';
import { routes } from './manifest/client.js';

init(document.querySelector('#sapper'), routes);

if (module.hot) module.hot.accept(); // enable hot reloading
// app/server.js (formerly server.js)
// Note that we're now using ES module syntax, because this
// file is processed by webpack like the rest of your app
import sapper from 'sapper';
import { routes } from './manifest/server.js';
// ..other imports

// we now pass the `routes` object to the Sapper middleware
app.use(sapper({
  routes
}));
// app/service-worker.js (formerly templates/service-worker.js)
import { assets, shell, timestamp, routes } from './manifest/service-worker.js';

// replace e.g. `__assets__` with `assets` in the rest of the file

Templates and error pages

In previous versions, we had templates/2xx.html, templates/4xx.html and templates/5xx.html. Now, we have a single template, app/template.html, which should look like your old templates/2xx.html.

For handling error states, we have a 'special' route: routes/_error.html.

This page is just like any other, except that it will get rendered whenever an error states is reached. The component has access to status and error values.

Note that you can now use this.error(statusCode, error) inside your preload functions.

Webpack configs

Your webpack configs now live in a webpack directory:

  • webpack.client.config.js is now webpack/client.config.js
  • webpack.server.config.js is now webpack/server.config.js

If you have a service worker, you should also have a webpack/service-worker.config.js file. See sapper-template for an example.

<0.9 to 0.10

app/template.html

  • Your <head> element must contain %sapper.base% (see (base URLs)
  • Remove references to your service worker; this is now handled by %sapper.scripts%

Pages

  • Your preload functions should now use this.fetch instead of fetch. this.fetch allows you to make credentialled requests on the server, and means that you no longer need to create a global.fetch object in app/server.js.

0.11 to 0.12

In earlier versions, each page was a completely standalone component. Upon navigation, the entire page would be torn down and a new one created. Typically, each page would import a shared <Layout> component to achieve visual consistency.

As of 0.12, this changes: we have a single <App> component, defined in app/App.html, which controls the rendering of the rest of the app. See sapper-template for an example.

This component is rendered with the following values:

  • Page — a component constructor for the current page
  • props — an object with params, query, and any data returned from the page's preload function
  • preloadingtrue during preload, false otherwise. Useful for showing progress indicators

Sapper needs to know about your app component. To that end, you will need to modify your app/server.js and app/client.js:

// app/server.js
import polka from 'polka';
import sapper from 'sapper';
import serve from 'serve-static';
import { routes } from './manifest/server.js';
+import App from './App.html';

polka()
  .use(
    serve('assets'),
-		sapper({ routes })
+		sapper({ App, routes })
  )
  .listen(process.env.PORT);
// app/client.js
import { init } from 'sapper/runtime.js';
import { routes } from './manifest/client.js';
+import App from './App.html';

-init(target: document.querySelector('#sapper'), routes);
+init({
+	target: document.querySelector('#sapper'),
+	routes,
+	App
+});

Once your App.html has been created and your server and client apps updated, you can remove any <Layout> components from your individual pages.

0.13 to 0.14

The 4xx.html and 5xx.html error pages have been replaced with a single page, _error.html. In addition to the regular params, query and path props, it receives status and error.

0.14 to 0.15

This release changed how routing is handled, resulting in a number of changes.

Instead of a single App.html component, you can place _layout.html components in any directory under routes. You should move app/App.html to routes/_layout.html and modify it like so:

-<!-- app/App.html -->
+<!-- routes/_layout.html -->

-<Nav path={props.path}/>
+<Nav segment={child.segment}/>

-<svelte:component this={Page} {...props}/>
+<svelte:component this={child.component} {...child.props}/>

You will then need to remove App from your client and server entry points, and replace routes with manifest:

// app/client.js
import { init } from 'sapper/runtime.js';
-import { routes } from './manifest/client.js';
-import App from './App.html';
+import { manifest } from './manifest/client.js';

init({
  target: document.querySelector('#sapper'),
-	routes,
-	App
+	manifest
});
// app/server.js
import sirv from 'sirv';
import polka from 'polka';
import sapper from 'sapper';
import compression from 'compression';
-import { routes } from './manifest/server.js';
-import App from './App.html';
+import { manifest } from './manifest/server.js';

polka()
  .use(
    compression({ threshold: 0 }),
    sirv('assets'),
-		sapper({ routes, App })
+		sapper({ manifest })
  )
  .listen(process.env.PORT)
  .catch(err => {
    console.log('error', err);
  });

preload functions no longer take the entire request object on the server; instead, they receive the same argument as on the client.

0.17 to 0.18

The sapper/webpack/config.js file (required in the webpack/*.config.js files) is now sapper/config/webpack.js.

0.20 to 0.21

  • The app directory is now src
  • The routes directory is now src/routes
  • The assets directory is now static (remember to update your src/server.js file to reflect this change as well)
  • Instead of having three separate config files (webpack/client.config.js, webpack/server.config.js and webpack/service-worker.config.js), there is a single webpack.config.js file that exports client, server and serviceworker configs.

0.21 to 0.22

Instead of importing middleware from the sapper package, or importing the client runtime from sapper/runtime.js, the app is compiled into the generated files:

// src/client.js
-import { init } from 'sapper/runtime.js';
-import { manifest } from './manifest/client.js';
+import * as sapper from '../__sapper__/client.js';

-init({
+sapper.start({
  target: document.querySelector('#sapper'),
-	manifest
});
// src/server.js
import sirv from 'sirv';
import polka from 'polka';
import compression from 'compression';
-import sapper from 'sapper';
-import { manifest } from './manifest/server.js';
+import * as sapper from '../__sapper__/server.js';

const { PORT, NODE_ENV } = process.env;
const dev = NODE_ENV === 'development';

polka() // You can also use Express
  .use(
    compression({ threshold: 0 }),
-		sirv('assets', { dev }),
+		sirv('static', { dev }),
-		sapper({ manifest })
+		sapper.middleware()
  )
  .listen(PORT, err => {
    if (err) console.log('error', err);
  });
// src/service-worker.js
-import { assets, shell, routes, timestamp } from './manifest/service-worker.js';
+import { files, shell, routes, timestamp } from '../__sapper__/service-worker.js';

In addition, the default build and export directories are now __sapper__/build and __sapper__/export respectively.