One Codebase, Both Sides of the Render

SvelteKit is built to handle both server-side rendering (SSR) and client-side rendering (CSR) within a single project. This is a significant departure from the older approach, where building an SSR app with Svelte meant maintaining two separate codebases: one for a Node server with a templating engine like Handlebars or Mustache, and another for the client-side Svelte application fetching data from that server.

That traditional split introduces two immediate problems:

  1. The overall application becomes more complex because you are effectively managing two systems.
  2. Sharing logic and data between the client and server code is cumbersome compared to a more integrated architecture.

A Framework That Handles the Heavy Lifting

SvelteKit removes the need to juggle two applications. It manages the server and client concerns internally, which brings several key advantages:

  • Each route can include a page.server.ts file to execute server-side code and seamlessly return data to your client components.
  • When using TypeScript, SvelteKit auto-generates types that are shared between the client and server, ensuring type safety across the board.
  • You can choose your rendering strategy on a per-route basis, such as using SSR for public pages and CSR for admin sections.
  • Routing is file-system based, making it simple to define new routes by just adding files and folders.

Building a Job Board

To illustrate this workflow, we can build a job board. The goal is a straightforward app where SvelteKit fetches data from a local JSON file and renders it on the server side before sending the HTML to the browser.

Project Initialization

First, we need to create a new SvelteKit project. We can do this directly from the command line, which will scaffold a new folder called job-list-ssr-sveltekit and prompt us with a few configuration options:

  1. We select the "Skeleton Project" template.
  2. For type-checking, we can choose the “TypeScript syntax” option. This is optional, and you can select `none` if you prefer.
  3. There are additional options for adding tools like ESLint, Prettier, Playwright, and Vitest. We will skip these since we only need the core app architecture for our example.

Once the template is set up, we install the required Svelte and SvelteKit dependencies.

It’s worth noting that looking at the project's package.json file will reveal that SvelteKit is listed under devDependencies. This is because Svelte and SvelteKit function as a compiler, transforming all your .js and .svelte files into optimized JavaScript. This means the Svelte package itself is not needed in the production bundle. The final output only contains your app's code, resulting in a smaller, faster-loading bundle.

Defining the Job Data

The information the app renders needs to come from somewhere. To keep the example clear, we’ll follow the plan of using a JSON file. We’ll structure each job item with the following fields:

  • Job title
  • Job description
  • Company name
  • Compensation

We’ll place this data file in a new folder named data which we create inside the project’s src directory.

Creating the TypeScript Model

Since TypeScript is a common part of modern development, we should show how it integrates with SvelteKit. We define our data types in a new models.ts file located in the src folder. This allows other components and pages to import and use these types with full type-checking.

This file defines two main types:

  1. JobList: Contains an array of job items.
  2. JobItem: Contains the specific structure of a single job (the properties we defined in the data section).

Constructing the Main Page and Components

Now we can start writing the code for the user interface. The main job board page is the +page.svelte file inside the src/routes directory. The file-based routing system automatically maps this file's location to a URL path. For now, we'll give it a page title and some basic CSS to center the content, as the primary goal is to understand the data flow. That’s the clean DX benefit: we don't have to write our own router code.

Since the job board is just a container, we need a dedicated component to render each job's details. We'll create this as JobDisplay.svelte inside a new components folder. This component is responsible for receiving a single job's data and rendering its markup. It will use a job prop that has the type of our JobItem from the model file.

Fetching Data with a Server Load Function

For our SSR application, the data fetching must happen on the server. SvelteKit offers a special load function for that purpose. We implement this in a file named +page.server.ts in the project’s routes directory. This suffix tells SvelteKit to run the code on the server when that specific route is requested.

Within this file, we:

  1. Import the JSON data we created earlier (in a real scenario, this would likely be a call to a database or external API).
  2. Import the JobsList TypeScript model.
  3. Assign the JSON data to a job_list variable.
  4. Define and export a load function that returns an object with the job_list. SvelteKit will automatically call this load function whenever the page is requested, fetching the data server-side and preparing the HTML.

Receiving Data in the Page

SvelteKit simplifies the transfer of data from server to client. After defining the server load function, we can access its return value directly in our page. We do this by importing a special type, PageServerData, from the automatically generated ./$types module. This type reflects the structure of the object returned by +page.server.ts.

Now we can update the main page’s markup. Within the +page.svelte file, we can use the following pattern:

  1. Import our new JobDisplay component.
  2. Import the auto-generated type from ./$types for the `data` prop that SvelteKit provides to the page. This prop contains everything returned from our server file.
  3. Utilize Svelte’s {#each} syntax to iterate over the data.job_list array, rendering a JobDisplay component for each job object.

By running the app, you can see the final result: the server retrieves the data, incorporates it into HTML markup, and delivers a fully populated page to the browser. This cycle is seamless from a developer's perspective but highlights the robust architecture SvelteKit provides for building SSR applications without the customary complexity.

From Build to Live App

The moment to compile and bundle the project has arrived. SvelteKit uses the same Terminal command as most other frameworks, so the workflow should feel immediately familiar:

npm run build
Build output
(Large preview)

Note: You might see a warning that reads “Could not detect a supported production environment.” That is expected at this stage and will be resolved shortly.

Before moving to a production server, run npm run preview to test the latest built version locally:

npm run preview

This local preview step is a solid way to validate the build before it goes live.

Choosing a Deployment Target

To push the app to a server, you’ll need an adapter. SvelteKit provides adapters for various environments, and Netlify is used here purely as a convenient example. You can review the full list of supported adapters in the official docs.

For Netlify, install the dedicated adapter plugin:

npm i -D @sveltejs/adapter-netlify

This naturally adds a new dependency to your package.json file. Then, update svelte.config.js to import and use it:

import adapter from ’@sveltejs/adapter-netlify’;
import { vitePreprocess } from ’@sveltejs/kit/vite’;

/** @type {import(’@sveltejs/kit’).Config} */
const config = {
    preprocess: vitePreprocess(),

    kit: {
        adapter: adapter({
            edge: false, 
            split: false
        })
    }
};

export default config;

Here’s what the configuration does:

  1. The adapter is imported from adapter-netlify.
  2. The new adapter is assigned to the adapter property inside the kit object.
  3. The edge boolean flag enables deployment to a Netlify edge function.
  4. The split boolean flag controls whether each route becomes a separate edge function.

Netlify-Specific Setup

The remaining configuration steps are exclusive to Netlify, so they’re covered separately here.

Create a netlify.toml file at the project’s root level:

[build]
  command = "npm run build"
  publish = "build"

This file introduces a new deployment alias for Netlify. It also enables you to manage deployments from your Netlify dashboard. To do that:

  1. Create a new project in Netlify.
  2. Choose the “Import an existing project” option.
  3. Grant Netlify access to the project repository, whether it’s hosted on GitHub or another service.
Netlify deploy
(Large preview)

With the netlify.toml file in place, you can stick with the default settings and hit “Deploy” directly in Netlify.

After the deployment finishes, visit the site via the URL Netlify provides. You should see the final result:

Final output
(Large preview)

There’s one satisfying check to run: open DevTools in the browser and look at the HTML. You’ll notice it already contains the data fetched from the JSON file, confirming the server-side rendering is working as intended.

HTML SSR screenshot
(Large preview)

Note: The complete project source code is available on GitHub. Each step covered here is tracked as a separate commit in the main branch for easy reference.

Wrapping Up

This walkthrough covers the fundamentals of server-side rendered apps and the concrete steps to build and deploy one with SvelteKit. The process — from configuration to live deployment — shows how SvelteKit fits neatly into a production workflow, and the adapter system keeps the door open for switching deployment targets when your needs change.