In this tutorial, you'll learn how to build and launch a website from scratch with Svelte and SvelteKit.
Why learn SvelteKit?
According to the State of JS 2021 survey, Svelte is the fastest-growing front-end development framework. Svelte is growing with good reason. It is designed for developer ergonomics and web best practices. With Svelte, you’ll make fast, accessible websites.
As the official back-end framework for Svelte, SvelteKit saw immediate uptake by developers when it was first released in beta. SvelteKit 1.0 was released in December of last year and has grown quickly.
Svelte and SvelteKit have many of the same features as other popular web development frameworks, like components, scoped CSS, and file-system based routing. Svelte also includes shortcuts for styling, reactivity, animations, and templating.
What is Svelte?
You might remember Svelte from our Optimized Dev challenge over the summer, but if not (or if you’re new to the challenge 👋), here’s a refresher.
At its core, Svelte is a code compiler. Whereas other frameworks like React and Vue.js generally add code to your web app to make it work in the user's browser, Svelte compiles the code that you write when you build your app. In doing so, it creates very small files and fast websites.
As a compiler, when you write Svelte, it looks a little strange. Here's an example of a .svelte
file:
<script> let name = 'world'; </script> <h1> Hello {name}! </h1>
That will generate a component that looks like this:

Svelte looks like HTML, with <script>
and <style>
tags included, but it also adds syntax to make your HTML dynamic — inside curly braces. All of this code gets transformed into vanilla HTML, CSS, and JavaScript with Svelte's compiler.
(If you want more of a refresher on Svelte, head on over to our Svelte tutorial, which takes you through some of its cool features.)
What is SvelteKit?
SvelteKit is a back-end framework for Svelte. While Svelte handles code that runs in the browser — like interactivity and reactivity — SvelteKit gives you infrastructure for the server hosting your app. SvelteKit will provide routing, layouts, static-site generation, API endpoints, and other app features that can only run on a server.
SvelteKit is also great because it has extremely fast hot reloading in development mode. When you click save, changes can appear instantaneously.
An overview of our SvelteKit project
In this project, we’ll build a simple info website with SvelteKit. The website will have a dynamic, component-based layout and dynamic routing. The website content will be managed in a CMS, so that the website manager can add, edit, and remove pages freely. And, we’ll optimize everything for great performance.
In the process of making this website, we’ll learn how to create:
- The website’s overall style with layouts and global styles
- Beautiful UI elements using scoped styles, style shorthands, class shorthands, Svelte blocks (
{#if}
,{#each}
,{@const}
, and{@html}
), and rich text - A clean information architecture using active links, file-system based routing, and
<title>
tags using the<svelte:head>
element - A great editor experience by using a headless website builder
- Great page speed scores with responsive images and data from a headless website builder
- A great developer experience using a component-based website architecture and SvelteKit’s
$app
module
In the end, we’ll deploy the site so you’ll have your own SvelteKit website live online.
Not signed up for The Optimized Dev?
Staying on the leading edge of web development just got easier. Explore new tech with a fun, scaffolded coding challenge that lands in your inbox once a month.
Setting up our SvelteKit project
I’ve created a boilerplate to help you get started. To install it, run this command in your terminal:
npx prismic-cli@latest theme --theme-url https://github.com/gracemiller23/sveltekit-challenge
Follow the prompts to create your project. This will create a basic boilerplate for you, which includes your Svelte and SvelteKit configuration, a CMS integration, and a few empty components. You’ll be asked to create a “repository name” — you’ll want to make a note of this for a configuration step that will come later.
Once the setup is complete, open the folder you created in your code editor (I use VSCode).
Install dependencies:
npm install
Then run the app in dev mode:
npm run dev
You can now open a basic app in your browser at http://localhost:5173.
A tour of our SvelteKit file structure
All of the relevant files for this project live in the src/
directory. When you get started, you’ll have these files:
src/ ├── routes/ │ └── [...path]/ │ └── +page.svelte └── lib/ ├── slices/ │ ├── FunHeading.svelte │ ├── ImageBullets.svelte │ ├── TextBlock.svelte │ ├── TextBox.svelte │ └── index.js └── prismicio.js
Library
src/lib
contains code shared between multiple pages. You can access this directory with the $lib/
alias. There are two things to note here:
src/lib/prismicio.js
contains some boilerplate for our Prismic CMS connection.src/lib/slices/
contains the components that will render the content for our website, which are called “Slices” in Prismic.
Routing
SvelteKit uses file-system based routing. Each page is defined by a +page.svelte
file, and the route for the page is the file’s directory. So src/routes/about/+page.svelte
is the page component for the route /about
.
What if you want to use one component for more than one route? Then you put the name of the directory in square brackets: src/routes/[path]/+page.svelte
. Then, the file will render for any route that matches the pattern. In this case, it would render for /about
and /contact
, but not /blog/hello-world
.
What if you want one component for more than one route segment? For instance, what if you want the same component for /about
and also /blog/hello-world
? For that, you use Svelte’s rest parameter routing, by prefixing the route name with three periods: src/routes/[...path]/+page.svelte
. This component will render for every route on the website.
That’s what we’re doing in this project. All of the pages have the same layout, so we only need one page component to render for every route. src/routes/[...path]/+page.svelte
is the page component.
We’ll add to this as we go.
Create a layout
In your project, the file src/routes/[...path]/+page.svelte
is the main page component of your app. This component will render a page for any path, so we can use it for the homepage /
and any sub-pages, like /contact
.
In SvelteKit, we can put global elements — like footers, headers, and global styles, alongside the +page.svelte
file in a +layout.svelte
file. The layout will wrap the corresponding route and any child routes.
Create the layout file
Create the file src/routes/[...path]/+layout.svelte
.
When you create the file, you’ll notice that your page content disappears. That’s because your layout file is empty.
Add one element to your layout:
<!-- src/routes/[...path]/+layout.svelte --> <slot />
In Svelte, the <slot>
element is a placeholder that will render content from a parent component. In a layout file, the <slot>
is where your page content will be inserted, so the layout component effectively wraps around your page.
<!-- src/routes/[...path]/+layout.svelte --> <script> import pico from '@picocss/pico'; </script> <div id="page"> <nav class="center"> <a>My Site</a> </nav> <main class="center"> <slot /> </main> <footer class="center">© Acme Corporation 2022</footer> </div> <style> :global(html) { overflow-y: scroll; } #page { display: flex; flex-direction: column; min-height: 100vh; gap: 5vw; } .center { padding-left: max(1rem, calc(50vw - 350px)); padding-right: max(1rem, calc(50vw - 350px)); } nav, footer { text-transform: uppercase; font-size: 0.7rem; letter-spacing: 0.1px; } nav { background: rgba(0, 0, 0, 0.4); font-weight: 500; padding-top: 1rem; padding-bottom: 1rem; } footer { text-align: center; padding: 3rem 0; color: #777; margin-top: auto; } </style>
What’s going on here?
- First, we’re importing Pico, and Svelte applies the CSS globally.
- Then, we have some markup to give our page structure, putting our
<slot>
element in the middle, where we want the page content to display. - Finally, we have some hand-written CSS, including one style rule that uses the
:global()
CSS pseudo selector, which will apply the rule globally.
We now have a very basic page layout.
Fetch navigation from Prismic
Next, we’re going to fetch data from our CMS — Prismic. A CMS allows you to manage the content of your website — including complicated elements like responsive images, rich text, internal links, and third-party data integrations. Using a CMS means that anyone can edit the website, so you can make changes quickly and safely without opening up your codebase.
First, in scr/lib/prismicio.js
update the repoName
variable to contain the repository name you created earlier.
Next, we’ll make the <nav>
display links managed in Prismic. To do so, create the file src/routes/[...path]/+layout.server.js
. This file will run some code server-side to supply data to the layout component. Inside this file, paste in this code:
// src/routes/[...path]/+layout.server.js import createClient from '$lib/prismicio'; export async function load({ fetch, request }) { const client = createClient({ fetch, request }); const { data } = await client.getSingle('settings'); return data; }
This code imports the client that we configured in src/lib/prismicio.js
and then uses it to fetch our settings file from Prismic’s cloud (where your content is stored in what they call a “repository”). It then returns the settings variable for use in the layout component.
Render navigation links
In our script file, we’ll add four lines to:
- Import some tools from
@prismicio/helpers
- Import the
page
utilities from SvelteKit’s$app
module to identify the current page in the navigation - Declare a prop with export let data to receive data from
+layout.server.js
- Destructure the
navigation
from ourdata
prop
Here’s what it will look like:
<!-- src/routes/[...path]/+layout.svelte --> <script> import pico from '@picocss/pico'; import * as prismicH from '@prismicio/helpers'; import { page } from '$app/stores'; export let data; const { navigation } = data; </script>
Next, we can update the navigation element:
Svelte handles rendering logic with a collection of curly-bracket blocks:
{#if condition} ... {:else if otherCondition} ... {:else} ... {/if}
for conditional rendering{#each array as item} ... {/each}
for lists{#await promise} ... {:then value} ... {:catch error} ... {/await}
for promises{@html myHTML}
for injecting HTML{myJavaScriptVariable}
for injecting the result of a JavaScript variable (or expression)
You can see examples for these blocks in the Svelte Society’s cheatsheet.
Here, we’ll use an {#each}
block to loop over our navigation items:
<!-- src/routes/[...path]/+layout.svelte --> <nav class="center"> {#each navigation as item} <a href={prismicH.asLink(item.link)}>{item.link_label}</a> {/each} </nav>
Render the active link as a <span>
However, it would also be nice to handle the current page item a little differently. We can add some logic to check if the navigation link is equal to the current page link. If so, we can render it as a <span>
:
<!-- src/routes/[...path]/+layout.svelte --> <nav class="center"> {#each navigation as item} {@const activeLink = item.link.url === $page.url.pathname} {#if activeLink} <span>{item.link_label}</span> {:else} <a href={prismicH.asLink(item.link)}>{item.link_label}</a> {/if} {/each} </nav>
Here, we use Svelte’s {@const}
block to declare a local variable: a boolean identifying whether the current item matches the current page. If it does, we just render a <span>
. If not, we render an <a>
.
Now, we have a navigation menu with links managed in a CMS. Next, we need to render the content for each page.
Fetch page data
Your page component is src/routes/[...path]/+page.svelte
. It contains a little boilerplate. Inside the <script>
tag we have four imports
and an export:
- We import a
<SliceZone>
component. This is the component that you will use to render content from Prismic. - We import the
dev
property from SvelteKit’s$app
module. This is a boolean that will betrue
in development andfalse
in production. We pass this as a prop to the<SliceZone>
to show errors in development but not production. - We import some
components
from the$lib
directory. Thesrc/lib
directory is available anywhere in your app as$lib
. Inside, we have aslices
folder that contains the boilerplate for four Prismic Slices. These Slices will render the content of our app. - Finally, we export a variable called
data
. This is how we declare props in Svelte, and thedata
prop is how this page component will receive dynamic data.
After the <script>
tag, there is some markup in {
curly brackets }
.
Here, we’re using an {#if}
block to conditionally render our <SliceZone>
component only if we have a document
from the API. In Prismic, each Document represents a page or a settings file.
To proceed, we’ll fetch a document from the Prismic API the same way we fetched our navigation menu. Create src/routes/[...path]/+page.server.js
and paste in the following code:
//src/routes/[...path]/+page.server.js import createClient from '$lib/prismicio'; export async function load({ fetch, request, params }) { const uid = params.path.split('/').at(-1) || 'homepage'; const client = createClient({ fetch, request }); const document = await client.getByUID('page', uid); return { document }; }
This code will parse the URL path and query the API for a document with a matching path. For the homepage path — /
— it will query the API for the document with the UID of homepage
.
Now, we have data in our page component. Your page should refresh with Slices now rendering. If you’d like, you can delete the conditional logic from your page component in src/routes/[...path]/+page.svelte
to simplify it:
<!-- src/routes/[...path]/+page.svelte --> <script> import { SliceZone } from '@prismicio/svelte'; import { dev } from '$app/environment'; import * as components from '$lib/slices'; export let data; </script> <SliceZone slices={data?.document?.data?.body} {components} {dev} />
Add a page title
Every web page should have a <title>
tag in the header. To inject that tag, you can use Svelte’s <svelte:head>
element. Import your tools from @prismicio/helpers
in order to render your title element, then add a <svelte:head>
element containing a <title>
tag to your page:
<!-- src/routes/[...path]/+page.svelte --> <script> import { SliceZone } from '@prismicio/svelte'; //import Prismic's helpers package import * as prismicH from '@prismicio/helpers'; import { dev } from '$app/environment'; import * as components from '$lib/slices'; export let data; </script> //inject your title tag <svelte:head> <title>{prismicH.asText(data?.document?.data?.title)}</title> </svelte:head> <SliceZone slices={data?.document?.data?.body} {components} {dev} />
Inside the <title>
tag, we’re rendering the document’s title property as plain text using prismicH.asText()
.
Create Slices
The project boilerplate includes four Slices:
src/lib/slices/FunHeading.svelte
src/lib/slices/ImageBullets.svelte
src/lib/slices/TextBlock.svelte
src/lib/slices/TextBox.svelte
Instead of coding a single page component, we’ll code each of these Slices individually. Then you can use Prismic’s custom website builder interface to add and rearrange these Slices to build pages using the components. (You can see the interface by visiting your dashboard and checking out the repository you created earlier.)
Code the TextBlock Slice
To start with, open src/lib/TextBlock.svelte
. This Slice is going to display formatted text. Delete the contents of the file, and then paste in this code:
<!-- src/lib/TextBlock.svelte --> <script> import * as prismicH from '@prismicio/helpers'; export let slice; </script> {@html prismicH.asHTML(slice.primary.text)}
Here, we’re importing a Prismic utility to handle Rich Text. We use the utility — asHTML()
— to generate a string of formatted HTML using content from the Prismic API and then we use Svelte’s {@html}
block to inject the string into our app.
If you refresh your homepage, you should see that one of your Slices is now rendering content with formatting.
Code the TextBox Slice
Next, we’ll code the TextBox Slice. This is going to be a callout box with an emoji icon in the corner. For starters, in src/lib/TextBox.svelte
create an <article>
element. Inside, create two <divs>
: one with the class of "emoji"
, to render our emoji; and one with the class of "text"
, to render our text just like we did in the TextBlock
Slice:
<!-- src/lib/TextBox.svelte --> <script> import * as prismicH from '@prismicio/helpers'; export let slice; </script> <article> <div class="emoji">{slice.primary.emoji}</div> <div class="text"> {@html prismicH.asHTML(slice.primary.text)} </div> </article> <style> article { display: flex; align-items: flex-start; gap: 1.2rem; padding: 2rem 2rem 2rem 1.4rem; } .emoji { font-size: 2rem; } </style>
Finally, we’ll make the layout more customizable by giving editors the ability to choose whether the emoji goes on the left or right side of the box.
Svelte includes a helpful class shorthand to facilitate this. By writing class:variable
(using a colon) we can apply a class with the name of variable
conditionally only if variable
is true
:
<!-- src/lib/TextBox.svelte --> <script> import * as prismicH from '@prismicio/helpers'; export let slice; // Check if the emoji should be at right or left const reverse = slice.slice_label === 'emoji_right'; </script> <!-- Conditionally apply a class --> <article class:reverse> <div class="emoji">{slice.primary.emoji}</div> <div class="text"> {@html prismicH.asHTML(slice.primary.text)} </div> </article> <style> article { display: flex; align-items: flex-start; gap: 1.2rem; padding: 2rem 2rem 2rem 1.4rem; } .emoji { font-size: 2rem; } /* If the class is applied put the emoji at the right */ .reverse { flex-direction: row-reverse; padding: 2rem 1.4rem 2rem 2rem; } </style>
Code the ImageBullets Slice
Our third Slice is an image with some bullets next to it.
To serve optimized images responsively, we’ll use Prismic’s asImageWidthSrcSet()
helper, which will return an object containing a src
string and a srcset
string.
Paste in this code to create the basic template for your Slice in src/lib/slices/ImageBullets.svelte
:
<!-- src/lib/slices/ImageBullets.svelte --> <script> import * as prismicH from '@prismicio/helpers'; export let slice; const { src, srcset } = prismicH.asImageWidthSrcSet(slice.primary.image); </script> <div class="layout box"> <img {src} {srcset} alt={slice.primary.image.alt} /> <ul> <li>{prismicH.asHTML(slice.items[0].bullet, null, { paragraph: ({ children }) => children })}</li> </ul> </div> <style> img { width: 40%; border-radius: 1rem; aspect-ratio: 0.9; object-fit: cover; flex: 1.2; } .box { display: flex; justify-content: center; align-items: center; flex: 1; margin-top: 5vw; margin-bottom: 5vw; } ul { margin: 0; padding-left: 2rem; flex: 1; } .box li { font-weight: 500; color: black; padding-left: 0.5rem; margin-bottom: 1rem; } .box li:last-child { margin-bottom: 0; } </style>
Note: The third parameter to the asHTML()
helper is an HTML Serializer. In this case, it is just removing the <p>
tags from paragraph elements to simplify our formatting. You can ignore it for the purpose of this tutorial.
Render a list
The bullet list in this Slice is only rendering the first element. Let’s update the code so that the entire list will render. To do this, we can use Svelte’s each block.
<!-- src/lib/slices/ImageBullets.svelte --> <div class="layout box"> <img {src} {srcset} alt={slice.primary.image.alt} /> <ul> {#each slice.items as item} <li>{prismicH.asHTML(item.bullet, null, { paragraph: ({ children }) => children })}</li> {/each} </ul> </div>
Keep challenging yourself
Add some flair
Each list item includes text and an emoji. Instead of using standard list bullets, punctuate each list item with an emoji.
Learn a Svelte shorthand
There are many ways to create an emoji-bullet list. In Svelte, one way you can do it is with the style shorthand. See if you can use Svelte’s style shorthand to render your emoji list.
Hint 1: Google “list-style-type” for insight into how to customize a list.
Hint 2: "
marks can be tricky here!
Code the FunHeading Slice
This is our most basic Slice. It’s just a text string that we’re going to use as the title.
<!-- src/lib/slices/FunHeading.svelte --> <script> import * as prismicH from '@prismicio/helpers'; export let slice </script> <h2>{slice.primary.text}</h2>
Now have fun!
This one is up to you. Use what you’ve learned about Svelte to create a fun title Slice. If you want an extra challenge, you could incorporate some kind of reactivity. Some examples:
- Change the color of the header when a user clicks on it
- Let the user select the font of the header
- Include time-sensitive text under the title, like “Good morning / good evening”
Deploy your SvelteKit site
Make sure your project is pushed to GitHub. You'll need to initialize your project as a git repository:
git init
Add all of your files to staging:
git add .
And commit them:
git commit -m "Init"
In GitHub, create a new repository and follow the instructions to push your project to GitHub.
Visit Netlify or Vercel and create an account or log in. Click New project or New site. Follow the instructions to deploy your new GitHub repo. You can leave the deploy settings as is.
Congratulations!
Once you deploy your site, it should be live! Congratulations on building a website with SvelteKit 🎉
To recap, here’s what we went over:
- You initialized a boilerplate from a Prismic theme
- You added a layout with markup, styles, and active links
- You integrated a CMS to safely and conveniently separate code from content
- You built an entire website with a component-based architecture
- You used SvelteKit’s advanced routing features to generate many pages from a single component
- You learned to use a bunch of Svelte features, including templating blocks, modules, and class and style shorthands
Share your project
Now that your project is online, we’d love to see it. Tag us on Twitter — @prismicio and @samlfair — and share your project on the Prismic Community Forum.
Resources
To dig deeper into SvelteKit, have a look at the official documentation for building a website with Prismic and Svelte and these handy resources:
FAQs about Svelte and SvelteKit
What is Svelte?
At its core, Svelte is a code compiler. Whereas other frameworks like React and Vue.js generally add code to your web app to make it work in the user's browser, Svelte compiles the code that you write when you build your app. In doing so, it creates very small files and fast websites.
As a compiler, when you write Svelte code, it looks a little strange. Svelte looks like HTML, with <script>
and <style>
tags included, but it also adds syntax to make your HTML dynamic — inside curly braces. All of this code gets transformed into vanilla HTML, CSS, and JavaScript with Svelte's compiler.
What is SvelteKit?
SvelteKit is a back-end framework for Svelte. While Svelte handles code that runs in the browser — like interactivity and reactivity — SvelteKit gives you infrastructure for the server hosting your app. SvelteKit will provide routing, layouts, static-site generation, API endpoints, and other app features that can only run on a server.
SvelteKit is also great because it has extremely fast hot reloading in development mode. When you click save, changes can appear instantaneously.
What is SvelteKit used for?
SvelteKit adds functionality to Svelte applications. Svelte is a JavaScript framework for building client-rendered user interfaces. SvelteKit adds server-side features, like static site generation, file-based routing, environment variables, error handling, and form actions.
In a nutshell, Svelte generates the user interface. SvelteKit handles everything else.
Is SvelteKit a full-stack web framework?
SvelteKit is the official application framework for Svelte. Used together, Svelte and SvelteKit form a full-stack web framework. Svelte handles the interfaces, while SvelteKit handles the back-end logic.
Should I use Svelte or SvelteKit?
Best practice would be to use Svelte and SvelteKit together.
While it’s possible to build websites and web apps using only Svelte, you will have limited functionality. SvelteKit adds useful conventions and features, like file-based routing, which will make your Svelte application development more productive.
Is SvelteKit production ready?
SvelteKit is production ready and has been stable since the release of 1.0 in December 2022.