Svelte is one of the fastest-growing web development frameworks, and Sveltekit is Svelte's web app development framework. In this tutorial, you'll learn how to build and launch a website from scratch with Svelte and Sveltekit.
But, head's up! Svelte is in beta, so it still has lots of bugs, and many of these steps are likely to change.
You can see the final code for this project on GitHub, and the final project live on Netlify.
What is Svelte?
Svelte is a JavaScript framework for creating web apps. 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 unique. Here's an example of a Svelte file:
<script>
let name = 'world';
</script>
<h1>Hello {name}!</h1>
That will generate a web page 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.
What is Sveltekit?
Currently the most popular application framework for Svelte is Sapper. Sveltekit is the new official framework developed by Svelte, superseding Sapper as Svelte's go-to framework. It adds key features like routing, layouts, and state management to Svelte.
Sveltekit allows you to make static sites, server-rendered sites, and even hybrid static-and-server-rendered web apps. It also has extremely fast hot reloading in development mode because of the way it bundles JavaScript.
Getting started
This tutorial draws heavily from the SK Incognito docs. Big thanks to SK Incognito for documenting Sveltekit in development.
Prerequisites
This tutorial assumes you're already familiar with the basics of HTML, CSS, and JavaScript.
You'll need Node Node 12 LTS or Node 14 LTS and npm installed. Here's how to install Node and npm on Mac and Windows.
Setup
In your terminal, run this command:
npm init svelte@next svelte-kit-app
# replace `svelte-kit-app` with whatever you like
You'll get a warning in the terminal that Sveltekit is in development. Take heed.
In the options, do not use TypeScript, Less, or SCSS. As Sveltekit is still in alpha, we'll stick with plain 'ol CSS and JavaScript (for now).
Once the setup is complete, cd into your new folder and install dependencies:
npm i
Then run the app in dev mode:
npm run dev
You can now view Svelte's classic counter app in your browser at http://localhost:3000.
Make a new homepage
In Sveltekit, every page is a Svelte component. Svelte components are identified by their .svelte extension. Pages are stored in ~/src/routes
, and every component in this directory is a page in your app (we'll discuss that more in our section on routing, below).
To get started, we'll overwrite the content of ~/src/routes/index.svelte
(the app's homepage).
Erase everything on that page, and replace it with a simple heading, like this:
<main>
<h1>Homepage</h1>
</main>
When you click save, your browser should update instantly to display the new homepage:
For starters, we can edit this page as normal HTML. Try adding some content in-between the main tags to see what happens.
Then, try adding a style tag, with some CSS inside:
<main>
<div class="container">
<h1>Homepage</h1>
<hr>
<p>This is my new Sveltekit app.</p>
</div>
</main>
<style>
main {
font-family: sans-serif;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 0px 20px;
}
.container > * {
width: 100%;
max-width: 700px;
}
</style>
Add interactivity
Svelte makes it easier to create interaction between JavaScript and HTML. Declare a variable in your JavaScript and then include it in your HTML in curly braces.
Try updating your homepage like this:
<!-- ~/src/routes/index.svelte -->
<script>
let number = 2;
</script>
<main>
<div class="container">
<h1>{number}</h1>
<hr>
<p>This is my new Sveltekit app.</p>
</div>
</main>
<style>
main {
font-family: sans-serif;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 0px 20px;
}
.container > * {
width: 100%;
max-width: 700px;
}
</style>
You can make your app interactive by binding a variable to an input. Try replacing your <script>
and <main>
sections with this:
<script>
let text = 'Homepage';
</script>
<main>
<div class="container">
<h1>{text}</h1>
<input bind:value={text}>
</div>
</main>
This should create an input. When you change the value of the input, it should change the text in the heading.
In our app, we'll use JavaScript to fetch content from an API and display that content in HTML. In the next step, we'll set up a content API with Prismic.
Set up Prismic
If you don't already have a Prismic account, create one (it's free). Then, visit prismic.io/dashboard, and create a new repo.
Note: If you want to skip this step, you can use the Prismic repository that we're using in these examples. Just skip to the next section, "Fetch content in Svelte," and use this URL for your API endpoint: https://svelte-tutorial.cdn.prismic.io/api/v2
In your new repo, create a repeatable Custom Type called "page".
Add:
- a Key Text field called "title",
- a UID field called "uid",
- an Image field called "image",
- and a Rich Text field called "content".
✨ For a shortcut, you can copy-paste this JSON into the JSON Editor in the Custom Type builder, and it will configure all of these fields for you.
Now, go back to the main page of your repository and create your first document. Give it the title and UID "homepage". Then, add an image and some content, and click Save and Publish.
In a new tab, go to <your-repo-name>.prismic.io/api/v2
. This will open your API browser, allowing you to view your content on the Prismic API. Click Search documents, and the document you just published should appear:
Next, we're going to use Prismic's dev tools to fetch content and render it in Svelte.
Fetch content in Svelte
We will use two packages to work with Prismic in Svelte. @prismicio/client
is Prismic's basic JavaScript package for fetching content from the API, and prismic-dom is Prismic's basic package for rendering content in the DOM.
To install them, run:
npm i -D @prismicio/client prismic-dom
Next, create a directory at the root of your project, called utils/
. Inside, create a file called client.js
, and paste in this code:
import Prismic from '@prismicio/client';
const apiEndpoint = 'https://your-repo-name.cdn.prismic.io/api/v2';
const Client = Prismic.client(apiEndpoint);
export default Client;
Make sure to update your repo name in the apiEndpoint
variable.
Now, in our homepage component, ~/src/routes/index.svelte
, we'll add our new utilities. Update your component like this:
<!-- ~/src/routes/index.svelte -->
<script context="module">
import Client from './../../utils/client';
import PrismicDom from 'prismic-dom';
export async function load() {
const document = await Client.getByUID('page','homepage');
return {
props: {
document,
}
};
}
</script>
<script>
export let document;
</script>
<main>
<div class="container">
<h1>Homepage</h1>
<pre>{JSON.stringify(document, null, 2)}</pre>
</div>
</main>
<style>
main {
font-family: sans-serif;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 0px 20px;
}
.container > * {
width: 100%;
max-width: 700px;
}
</style>
What's happening here?
The most important thing is the load()
function. This function accepts information about the current page as arguments and returns props that you can use in your app.
Inside that function, we're using the Prismic query helper Client.getByUID()
to fetch your homepage document from the API and then returning that document as a prop.
Then, in a separate script tag, we initialize the response variable, which makes the variable accessible in our app.
If everything worked, you should now see your raw API response on the page.
Of course, we don't want to display JSON. Instead, let's display some human-readable content, by updating the <main>
tag, like this:
<main>
<div class="container">
<h1>{document.data.title}</h1>
</div>
</main>
Now, the title of your document is coming directly from Prismic.
Prismic provides utilities for working with Rich Text. prismic-dom
will convert your Rich Text field to HTML. Then, you can use Svelte's {@html }
utility to render it.
Update your <main>
tag like this:
<main>
<div class="container">
<h1>{document.data.title}</h1>
</div>
</main>
Now you should have some content on your page:
Style your content
Let's bind an inline style. We'll create a header section and use the image from Prismic as the background:
<!-- ~/src/routes/index.svelte -->
<script context="module">
import Client from './../../utils/client';
import PrismicDom from 'prismic-dom';
export async function load() {
const document = await Client.getByUID('page','homepage')
return {
props: {
document,
}
};
}
</script>
<script>
export let document;
</script>
<main>
<div class="header container" style="background-image: url('{document.data.image.url}')">
<h1>
{document.data.title}
</h1>
</div>
<div class="container">
<div class="text">
{@html PrismicDom.RichText.asHtml(document.data.content)}
</div>
</div>
</main>
<style>
main {
font-family: sans-serif;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 0px 20px;
}
.container > * {
width: 100%;
max-width: 700px;
}
.header {
color: white;
background-size: cover;
min-height: 25vw;
padding-top: 2rem;
justify-content: flex-end;
}
</style>
This looks good, but we've still got some unnecessary padding around the whole page. We can eliminate that with a CSS reset. In Svelte, CSS is scoped to the component. That means that the CSS in the component only styles that component. To style the document's <body>
tag (where the padding is), we need to import a style sheet.
For starters, create the folder ~/src/styles/
, and then add a file called reset.css
. Inside that file, paste the following code:
/* ~/static/styles/reset.css */
body {
margin: 0;
padding: 0;
}
* {
box-sizing: border-box;
}
Now, we need to import those styles to the component. You can do this by importing a CSS file in the component's second <script>
tag, like this:
<script>
import "./../styles/reset.css";
export let document;
</script>
When the page reloads, the hero image should be full-width.
Create a layout
However, we know that we want the CSS reset on every page of the website. We can abstract it out of our component by moving it to a layout.
If you have content that appears on every page --- like a header and footer --- you can move it into a layout file. To create a layout, create the file ~/src/routes/$layout.svelte
. In that file, paste this code:
<!-- ~/src/routes/$layout.svelte -->
<script>
import "./../styles/reset.css";
</script>
<div class="flex-layout">
<slot></slot>
<p class="footer">© Acme Corp, 1951</p>
</div>
<style>
.footer {
margin: 3rem 0;
text-align: center;
}
.flex-layout {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
}
</style>
The <slot>
element is where your page component will be inserted.
You can now remove the CSS reset from ~/src/routes/index.svelte
.
We can abstract more styles to the layout by creating a file called globals.css in ~/src/styles/
and importing that CSS to your layout:
<script>
import "./../styles/reset.css";
import "./../styles/globals.css";
</script>
For instance, you can delete the main
and .container
style rules from index.svelte
and these rules to ~/src/styles/globals.css
:
body {
font-family: sans-serif;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
padding: 0px 20px;
}
.container > * {
width: 100%;
max-width: 700px;
}
You've now created a page of your website with JavaScript and CSS.
Next, we'll look at how to add more pages.
Add multiple pages
In Svelte, every file in ~/src/routes/
represents a route in your app, so ~/src/routes/dogs/daschund.svelte
corresponds to your-site.com/dogs/daschund
.
File and folder names in square brackets are dynamic routes. So, the file ~/src/routes/dogs/[breed].svelte
would generate the route /dogs/*
. If you visit /dogs/doberman
, you will get the [breed].svelte
file.
Similarly, folder names can be dynamic. So, you could have ~/src/routes/[pet]/[breed].svelte
, which would be rendered for both /fish/goldfish
and /cat/siamese
.
To proceed, copy the contents of ~/src/routes/index.svelte
into a new file in the same folder called [uid].svelte
.
In [uid].svelte
, modify the load()
function to destructure the page prop (the props include information about the current page) like this:
export async function load({ page }) {
const { uid } = page.params;
const document = await Client.getByUID('page','homepage');
return {
props: {
document,
uid
}
};
}
page.params
is an object containing the variables from the URL path, which are specified in the file and folders with square brackets around their names. In the example above, /fish/goldfish
would have a page.params
object like this:
{
pet: "fish",
breed: "goldfish"
}
So, in the load()
function, we extract the uid
parameter from the page route and add it to the props
object, which the function returns.
Then, add uid
to the export statement in the second <script>
tag:
<script>
export let document, uid;
</script>
Finally, we can use the uid
variable somewhere in the template:
<main>
<div class="header container" style="background-image: url('{document.data.image.url}')">
<h1>
{uid}
</h1>
</div>
<div class="container">
<div class="text">
{@html PrismicDom.RichText.asHtml(document.data.content)}
</div>
</div>
</main>
Now, navigate to another page at the root-level of your website, like /doberman
or /tabby
. If everything is working, you should see the uid
param in your page:
However, it's not very useful to directly display the param on the page. Instead, we want to use it to dynamically display content.
In the Prismic query function, replace "homepage" with the uid
variable. Now, Svelte will query content from Prismic depending on the page path:
export async function load({ page }) {
const { uid } = page.params;
const document = await Client.getByUID('page',uid);
return {
props: {
document,
uid
}
};
}
Right now, there is only one document in Prismic. Go back to your Prismic repo, and create a page with the UID "about", and add some content to the page. Now, if you go to /about
, you should see your new about page.
In your <main>
tag, remove the uid
variable and replace the document title:
<main>
<div class="header container" style="background-image: url('{document.data.image.url}')">
<h1>
{document.data.title}
</h1>
</div>
<div class="container">
<div class="text">
{@html PrismicDom.RichText.asHtml(document.data.content)}
</div>
</div>
</main>
Create a nav component
Finally, we can add some site navigation. Let's create a nav component in ~/src/lib/
, called nav.svelte
. Paste in the following code:
<!-- ~/src/lib/nav.svelte -->
<nav class="container">
<div class="menu">
<a href="/">Home</a>
<a href="/about">About</a>
</div>
</nav>
<style>
nav {
width: 100%;
padding-top: 25px;
padding-bottom: 25px;
text-shadow: 0px 1px 3px rgba(0,0,0,.8), 0px 0px 6px rgba(0,0,0,.8);
}
nav a {
color: white;
text-decoration: none;
padding-right: 1rem;
}
</style>
Then, in your layout file, import the Nav
component and update your HTML and CSS like so:
<!-- ~/src/routes/$layout.svelte -->
<script>
import Nav from "./../lib/nav.svelte"
import "./../styles/reset.css";
import "./../styles/globals.css"
</script>
<div class="flex-layout">
<header class="absolute">
<Nav />
</header>
<slot></slot>
<p class="footer">© Acme Corp, 1951</p>
</div>
<style>
.absolute {
position: absolute;
width: 100%;
}
.footer {
margin: 3rem 0;
text-align: center;
}
.flex-layout {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: space-between;
}
</style>
Now, you should have two pages that you can click before and forth between:
Deploy your site
For now, deploying with Sveltekit is a little tricky, as it is still in development. Sveltekit has a handful of "adapters" to handle deployment in different environments. For now, we'll deploy statically on Netlify.
To install the necessary adapter, run the following command:
npm i -D @sveltejs/adapter-static
Then, in ~/svelte.config.cjs
, set the adapter:
const static = require('@sveltejs/adapter-static');
const pkg = require('./package.json');
/** @type {import('@sveltejs/kit').Config} */
module.exports = {
kit: {
// By default, `npm run build` will create a standard Node app.
// You can create optimized builds for different platforms by
// specifying a different adapter
adapter: static(),
// hydrate the <div id="svelte"> element in src/app.html
target: '#svelte',
vite: {
ssr: {
noExternal: Object.keys(pkg.dependencies || {}),
},
},
},
}
Next, 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 and create an account or log in. Click New site from Git. Follow the instructions to deploy your new GitHub repo.
For the deploy settings, ensure the build command is npm run build, and the build directory is build.
Once you deploy your site, it should be live!
See a bug?
If you notice a bug in this tutorial, please add a comment or get at me on Twitter: @samlfair
Thanks for reading :)
Resources
To keep learning, check out these resources:
- The code for this project on GitHub
- Prismic's JavaScript documentation
- Official Svelte docs
- Official Sveltekit docs
- Svelte Society Svelte Cheatsheet
- SK Incognito Docs
- Sveltekit on GitHub
This tutorial was originally published on the Prismic blog.
Top comments (2)
Hey Sam, me again :),
With regards the static adapter. It should install the
@next
version as the default one from NPM has issues.It should be
npm i -D @sveltejs/adapter-static@next
, resulting in version ^1.0.0-next.4 in the package.json fileBut there again, once it's out beta they probably going to fix that