How to Make a Website with Svelte and SvelteKit

samlfair

Sam Littlefair

Posted on March 24, 2021

How to Make a Website with Svelte and SvelteKit

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>
Enter fullscreen mode Exit fullscreen mode

That will generate a web page that looks like this:

Hello world svelte example in the browser

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then run the app in dev mode:

npm run dev
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

When you click save, your browser should update instantly to display the new homepage:

Screenshot of a Sveltekit app with the heading

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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.

Screenshot of the Prismic editor.

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:

Screenshot of the Prismic API browser.

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
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

You've now created a page of your website with JavaScript and CSS.

A screenshot of a homepage with a wide banner image.

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
      }
    };
  }
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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:

A screenshot of a webpage with

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
    }
  };
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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 || {}),
      },
    },
  },
}
Enter fullscreen mode Exit fullscreen mode

Next, make sure your project is pushed to Github. You'll need to initialize your project as a git repository:

git init
Enter fullscreen mode Exit fullscreen mode

Add all of your files to staging:

git add .
Enter fullscreen mode Exit fullscreen mode

And commit them:

git commit -m "Init"
Enter fullscreen mode Exit fullscreen mode

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:

This tutorial was originally published on the Prismic blog.

💖 💪 🙅 🚩
samlfair
Sam Littlefair

Posted on March 24, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related