Skip to main content

Build a fast reactive blog with Svelte and Sapper

Svelte and Sapper
(Image credit: Svelte)

Sapper is a framework built on top of Svelte. It focuses on speed out of the box with server rendering, link preloading, and the ability to create service workers. Combined with Svelte, the end result is a highly customisable, lightning fast site with a small footprint.

In this tutorial, we will be using Sapper to build a lightweight blogging site using Svelte components (see our how to start a blog post for some less technical tips on blogging). Svelte is a framework with a difference. It analyses the code at compile time and creates a set of modules in vanilla JavaScript, which avoids the need for a runtime. If you need further help with layouts then check out our post on how to get the perfect website layout

Download the tutorial files on FileSilo here

01. Install dependencies

Firstly, we need to download and install dependencies. It relies on Svelte to work and requires another framework to build the server, but the rest depends on the application being built. In this case, we need a few packages to help extract Markdown files later on.

Download the tutorial files (above(, find them on the command line and install the dependencies. 

> npm install

02. Build out server

Sapper is built in two parts – the client-side framework and the server-side rendering of that framework. This helps get that extra speed boost for those on slower networks. It is built using Node, which allows the Sapper middleware to do all the heavy lifting for us.

Open server.js and create the server with Express. Using their recommendations, we include compression to shrink the files we send and sirv to serve static files.

express()
  .use(
    compression({ threshold: 0 }),
    sirv(“static”, { dev }),
    sapper.middleware()
  )
  .listen(PORT);

03. Mount the application

On the client side, we need to tell Sapper where to mount the application. This is similar to React DOM’s render or Vue’s $mount methods. Inside client.js, start Sapper and mount it to the root <div> element. That element is included in the template file, which we will come to later on.

import * as sapper from “@sapper/app”;
sapper.start({
  target: document.getElementById(
   “root”)
});

04. Start the development server

Command line

Any errors during the development build process are flagged on the command line.

(Image credit: Matt Crouch)

With the basic server and client files set up, we can start the development server. This kicks off a build of the server, client and service worker files, and it will start up on port 3000 by default. Whenever a file changes, it will rebuild the part of the application that changed.

Run the server on the command line. Keep this window open while developing the site.

> npm run dev

05. Create a server route

Anything inside the “routes” directory will turn into a route for the application. Files with the .js extension will become what are called server routes. These routes have no UI, but instead are requested by the application to fetch data. In our case, we will use them to load up blog posts.

The routes/blog/index.json.js file will create the /blog.json endpoint on our server. Import the blog posts and create some JSON from them.

import posts from “./_posts.js”;
const contents = JSON.stringify(
  posts.map(post => ({
    author: post.author,
    image: post.image,
    title: post.title,
    slug: post.slug
  }))
);

06. Return the blog posts

Server routes export functions that correspond with HTTP methods. For example, to respond to a GET request, we would export a function called get from the server route file. Create a get function that responds with the data that we collected in the previous step in a JSON format. Open http://localhost:3000/blog.json in the browser and check the posts are coming through.

export function get(req, res) {
  res.writeHead(200, {
    “Content-Type”: “application/json”
  });

  res.end(contents);
}

07. Create index page

Pages in Sapper are regular Svelte components. Each component is a single file with a .svelte extension, and contain all the logic and styling for managing itself. Any JavaScript this component needs to run will live inside a <script> tag — just like any HTML page.


Inside routes/index.svelte, import a couple of other Svelte components that we can use to build this page. Export a posts variable that we will populate later.

<script>
  import Container from
   “../components/Container.svelte”;
  import PostSummary from
   “../components/PostSummary.svelte”;
  export let posts;
</script>

08. Fetch blog post data

With the page set up, we can start bringing in blog posts. We need to do this as soon as the page is loaded. In order for the server to be aware of this and then request this data when it renders the page, it needs to go in a separate <script context=”module”> tag.


On the server, Sapper will look for a preload function, and wait for it to complete before showing the page. Here, we are populating the posts variable from the previous step.

<script context=”module”>
  export async function preload() {
    const res = await
     this.fetch(“blog.json”);
    const data = await res.json();
    return { posts: data };
  }
</script>

09. Display post summaries

In Svelte, variables are reactive by default. This means that, as they update, our components will also update too. As the posts variable now holds an array of blog posts, we can loop over these and display them.

We can do this using an #each block. These will repeat what is inside the brackets for each item in an array. At the bottom of the component, outside of any tags, add the HTML to display the posts.

<Container>
  <ul>
    {#each posts as post}
      <li>
        <PostSummary {...post} />
      </li>
    {/each}
  </ul>
</Container>

10. Style the container

We can use components to contain any repeated logic and use them wherever they are needed – styles included. The <Container> component currently does not do anything, but we can use it to give a maximum width to the content inside it.

Open up components/Container.svelte, and add some styles inside a <style> tag. Any styles that we apply inside a component are scoped to that component, which means that we can use a generic selector.

<style>
  div {
    margin: 0 auto;
    padding: 0 1rem;
    max-width: 50rem;
  }
</style>

11. Define a <slot>

If a component is designed to be the parent to other components, we need a way to pass those components through. The <slot> element does just that, and can be placed anywhere that makes sense inside a component’s markup.

With <Container>, we are wrapping the contents in a styled <div>. Use <slot> inside the <div> to let everything else through.

<div>
  <slot />
</div>

12. Expose PostSummary props

Not every component is going to fetch some data. As we are loading the posts from the main page component, it can be passed through to the components it renders through its attributes.

Open components/PostSummary.svelte and define some attributes at the top of the file. These are getting filled in by the spread operator we added in step 09.

<script>
  export let author;
  export let image;
  export let slug;
  export let title;
</script>

13. Display blog post summary

When the attributes are populated, they are then accessed like any other variable. By having separate attributes for each part of the post summary, we make the markup easier to read.

At the bottom of the component, add some HTML to create the summary. The classes relate to the pre-defined styles.

<article>
<div class=”post-image” style=”
 background-image: url({image})” />
<div class=”body”>
  <div class=”author-image”>
    <img src={author.image}
     alt={author.name} />
  </div>
  <div class=”about”>
    <h1>{title}</h1>
    <span class=”byline”>by
     {author.name}</span>
  </div>
</div>
</article>

14.  Link to blog post

Sapper blog

Sapper can fetch the information for a link as the user hovers over it for a perceived performance benefit

(Image credit: Matt Crouch)

Unlike similar frameworks such as Next.js, Sapper works with regular anchor links. At build time, it is able to detect internal links, and also to make its own optimisations.

Update the markup from the previous step by wrapping it in a link. There is no need for you to create template strings in order to compose the slugged URL.

<a rel=”prefetch” href=”/blog/{slug}”>
  <article>…</article>
</a>

15. Fetch a post by slug

Sapper is able to create pages based on URL parameters. In our case, we link to /blog/slug, which means it renders the component at /routes/blog/[slug].svelte.

Inside that component, fetch the blog data like we did for the index page. The params object contains the parameters from the URL, which in this case is the slug.

<script context=”module”>
  export async function
   preload({ params }) {
    const res = await this.fetch(
     `blog/${params.slug}.json`);
    const data = await res.json();
  }
</script>

16. Error if post is not found

Unlike the index page, there is a chance there isn’t a blog post at the URL. In that case, we should display an error. Along with fetch, the preload method also includes error that changes the response to an error page instead.

At the end of the preload method, show an error if there is no post found. Otherwise, set the post variable for the page.

if (res.status === 200) {
  return { post: data };
} else {
  this.error(res.status,
    data.message);
}

17. Display the blog post

Internal links

Any internal links can be loaded asynchronously. This includes those written in Markdown.

(Image credit: Matt Crouch)

With the data fetched, we can now show the post on the page. Similar to the PostSummary component, we display each part of the post’s content inside curly brackets. At the bottom of the component, add some markup to display on the page.

<article>
  <Container>
    <div class=”title”>
      <h1>{post.title}</h1>
      <div class=”byline”>by
       {post.author.name}</div>
    </div>
    <img class=”post-image” src={post.image} alt=”” />
     {post.html}
  </Container>
</article>

18. Display HTML instead

Looking at the page now, everything displays correctly apart from the actual post content. The markdown conversion generates HTML, but we see that printed as text on the post itself. Sapper has a HTML parser built in for this case. Placing @html in front of it will use this parser.

{@html post.html}

19. Set page <title> value

Our blog functions correctly, but there are a couple of changes needed to finish it. One of those is to update the <title> on the page to relabel the tab it’s displayed in. 

Svelte supports a <svelte:head> element, which injects anything inside of it into the <head> of the page. Use this to set the title of the post as <title>.

<svelte:head>
  <title>{post.title} |
   Sapper Blog</title>
</svelte:head>

20. Updating the <title>

Every page goes through a template in order to stamp out the HTML for the rest of the page. This is where any setup such as font imports and meta tags would be entered. Open up template.html, and add in a hook for the contents of the <svelte:head> element from the previous step. Add this in just before the closing </head> tag.

<head>
  […]
  %sapper.head%
</head>The final thing we need to add is a layout for the blog. Instead of wrapping each page in a component, Sapper will look for a “_layout.svelte” file to do this job for us.
Inside _layout.svelte, import the <Header> component and show that at the top of every page. It provides a convenient link back to the homepage.

21. Add permanent header

The final thing we need to add is a layout for the blog. Instead of wrapping each page in a component, Sapper will look for a _layout.svelte file to do this job for us. Inside _layout.svelte, import the <Header> component and show that at the top of every page. It provides a convenient link back to the homepage.

<script>
  import Header from
   “../components/Header.svelte”;
</script>
<main>
  <Header />
  <slot />
</main>

This article was originally published in creative web design magazine Web DesignerBuy issue 290 now.