DEV Community

Cover image for A realistic SSR ready web app with Svelte + Sapper + StarWars API

A realistic SSR ready web app with Svelte + Sapper + StarWars API

Rajender Joshi
Learning frontend everyday.
・3 min read

How I built a SSR ready Star Wars app with Svelte in just few hours.

Svelte - As everyone know it's a "new" cool kid in the block. If you are still living under the rock, I recommend you to watch this presentation by Rich Harris - the creator of Svelte.

Sapper? Sapper is a Next.js equivalent for Svelte. It creates a bare-minimum boilerplate with routing, code-splitting, service worker and what not.

Recently I decided to try my hands on Svelte to create an app that supports both server side and client side rendering with those fancy shimmer effects.

So, let's get started.

Implementing SSR in Sapper is fairly simple and straight-forward. All you need is this code-block in your svelte files.

Adding context="module" in a script tag with an exported preload function implements SSR and CSR with pre-fetch(optional) like waving a magic wand. But there's a problem.

Lagging effect

When I click or hover(with rel=prefetch) on a menu item, Svelte tries to fetch the route chunk and resolves the api calls and only then navigates to the next page. It gives an impression that the app is either frozen or lagging which can be a frustrating user experience.

Clone the repo down below and checkout to the problematic commit hash(4bb9d18) to experience the issue.

GitHub logo crup / svelte-ssr-swapi

Demo project to integrate SSR with Svelte with workaround for preload lag.

$ git clone
$ cd svelte-ssr-swapi
$ git checkout 4bb9d18
$ yarn && yarn dev

How to fix this? With only a couple of hours of experience in Svelte, I don't know the right way but this is how I solved it me.

Create a Svelte store and define a key which will be an identifier for for distinguishing if the page is SSR or CSR.

In your routes file, instead of resolving the promise and returning the resolved response in cards key, read the SSR state and and return promise if isSSR is false.
Here's the boilerplate:

On your page level component set isSSR to false and resolve cards if it's a promise. Setting isSSR to false on first client-side render will enforce script tag with context="module" to return a promise instead of response.

With this approach we can now resolve apis on client side, show loaders/shimmers without any lag.

Now comes the worst part - rendering this list items. Since we now have an array(server-side) and a promise(client-side). I had to write handlers for both array and promise.

The final version is in master branch and it looks like this:
Final result


This is my very first attempt at Svelte and chances are that I may have done massive blunders like improperly accessing/updating/unsubscribing store, duplicating code in rendering cards and routes. There's a also a room for refactor in final branch. So, use this code at your own risk.

If there's a better way to achieve the same, feel free to create a PR and suggestions are welcome. :)

GitHub logo crup / svelte-ssr-swapi

Demo project to integrate SSR with Svelte with workaround for preload lag.

Discussion (3)

lgs profile image
Luca G. Soave

It looks currently broken. The FetchEvent for "" resulted in a network error response: the promise was rejected because it has been blocked by CORS policy.

Access to fetch at '' from origin '' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

rajenderjoshi profile image
Rajender Joshi Author

Hi, thanks for reporting the issue. Apparently, it looks like is out of service and there's a mirror on I've made the required changes and it should be working now.

divporter profile image
David Porter

I solved this by only fetching in the preload when isSSR is true. In the mounted hook, I do the reverse and fetch if it's CSR. In both cases the object type is an array. But it's only populated in preload on the server