Build a fast reactive blog with Svelte and Sapper
Combine server rendering with a minimalist framework that focuses on speed out of the box.
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. Or for other options, see these top website builders and remember, you'll also need to get your web hosting service on point.
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.
Get the Creative Bloq Newsletter
Daily design news, reviews, how-tos and more, as picked by the editors.
import * as sapper from “@sapper/app”;
sapper.start({
target: document.getElementById(
“root”)
});
04. Start the development server
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 (you may want to back them up in cloud storage). 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
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
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 content originally appeared in Web Designer.
Thank you for reading 5 articles this month* Join now for unlimited access
Enjoy your first month for just £1 / $1 / €1
*Read 5 free articles per month without a subscription
Join now for unlimited access
Try first month for just £1 / $1 / €1
Matt Crouch is a front end developer who uses his knowledge of React, GraphQL and Styled Components to build online platforms for award-winning startups across the UK. He has written a range of articles and tutorials for net and Web Designer magazines covering the latest and greatest web development tools and technologies.