See it live first

Before you scaffold anything, take a look at the live demo — sveltepress.github.io/sveltepress/blog-demo. Everything described on this page is already working there.

The navbar Blog demo is that finished showcase. The navbar Playground opens the Feature directory. Configuration, Writing posts, Features, and Customisation are the Blog theme Entries that auto-boot the Blog starter. This getting-started page is not a Playground Entry and has no Open in Playground control.

Source: packages/example-blog in the monorepo. After cloning the repository, run pnpm install and pnpm --filter @sveltepress/example-blog dev from the repository root. The demo starts on http://localhost:36739.

@sveltepress/theme-blog is a magazine-style blog theme with a left-rail sidebar, masonry post grid, per-post OG images, RSS, Pagefind search, and Giscus comments. This page walks you through scaffolding a working blog.

Install

npm install --save @sveltepress/theme-blog
sh
yarn add @sveltepress/theme-blog
sh
pnpm install @sveltepress/theme-blog
sh
bun add @sveltepress/theme-blog
sh

The theme is loaded by the Sveltepress Vite plugin:

npm install --save @sveltepress/vite
sh
yarn add @sveltepress/vite
sh
pnpm install @sveltepress/vite
sh
bun add @sveltepress/vite
sh

The theme requires @sveltejs/adapter-static because it generates a fully static site (prerendered HTML, JSON, RSS, OG images).

npm install --save @sveltejs/adapter-static
sh
yarn add @sveltejs/adapter-static
sh
pnpm install @sveltejs/adapter-static
sh
bun add @sveltejs/adapter-static
sh

Pagefind builds the local search index after Vite finishes:

npm install --save pagefind
sh
yarn add pagefind
sh
pnpm install pagefind
sh
bun add pagefind
sh

Configure Vite

vite.config.ts
import { import blogThemeblogTheme } from '@sveltepress/theme-blog'
import { const sveltepress: (options?: SveltepressVitePluginOptions) => PluginOptionsveltepress } from '@sveltepress/vite'
import { function defineConfig(config: UserConfig): UserConfig (+5 overloads)

Type helper to make it easier to use vite.config.ts accepts a direct

UserConfig

object, or a function that returns it. The function receives a

ConfigEnv

object.

defineConfig
} from 'vite'
export default function defineConfig(config: UserConfig): UserConfig (+5 overloads)

Type helper to make it easier to use vite.config.ts accepts a direct

UserConfig

object, or a function that returns it. The function receives a

ConfigEnv

object.

defineConfig
({
UserConfig.plugins?: PluginOption[] | undefined

Array of vite plugins to use.

plugins
: [
function sveltepress(options?: SveltepressVitePluginOptions): PluginOptionsveltepress({ SveltepressVitePluginOptions.theme?: ResolvedTheme | undefinedtheme: import blogThemeblogTheme({ title: stringtitle: 'My Blog', description: stringdescription: 'Thoughts on Svelte and the web.', base: stringbase: 'https://example.com',
author: {
    name: string;
    avatar: string;
    bio: string;
    socials: {
        github: string;
        twitter: string;
        rss: string;
    };
}
author
: {
name: stringname: 'Your Name', avatar: stringavatar: '/avatar.png', bio: stringbio: 'Short bio shown in the sidebar.',
socials: {
    github: string;
    twitter: string;
    rss: string;
}
socials
: {
github: stringgithub: 'your-handle', twitter: stringtwitter: 'your-handle', rss: stringrss: '/rss.xml', }, },
navbar: {
    title: string;
    to: string;
}[]
navbar
: [
{ title: stringtitle: 'Home', to: stringto: '/' }, { title: stringtitle: 'Timeline', to: stringto: '/timeline/' }, { title: stringtitle: 'Tags', to: stringto: '/tags/' }, ], }), }), ], })
ts

Configure SvelteKit

svelte.config.js
import adapter from '@sveltejs/adapter-static'
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'

export default {
  extensions: ['.svelte'],
  preprocess: [vitePreprocess()],
  kit: {
    adapter: adapter({
      pages: 'dist',
      assets: 'dist',
      fallback: '404.html',
    }),
    prerender: {
      handleMissingId: 'ignore',
      handleUnseenRoutes: 'ignore',
    },
    paths: {
      base: process.env.BASE_PATH ?? '',
      relative: false,
    },
  },
  compilerOptions: {
    runes: true,
  },
}
js

BASE_PATH lets you deploy under a subpath (e.g. GitHub Pages project site). Leave the env var unset for root deployments.

Write your first post

Create src/posts/hello-world.md:

src/posts/hello-world.md
---
title: Hello world
date: 2026-04-17
tags: [intro]
category: meta
excerpt: First post on the new blog.
---

# Hello

Welcome to my blog. Everything is markdown.
md

Routes auto-scaffolded

On the next vite dev or vite build, the theme writes these files if they don't exist. Edit them freely — the scaffolder only creates missing files.

PathPurpose
src/routes/+layout.tsEnables prerender + trailingSlash: 'always'
src/routes/+layout.svelteWraps pages in GlobalLayout
src/routes/+page.{server.ts,svelte}Paginated home
src/routes/page/[n]/...Page 2+ of the list
src/routes/posts/[slug]/...Individual post pages
src/routes/tags/+page.svelteTag index
src/routes/tags/[tag]/...Posts for a tag
src/routes/categories/[cat]/...Posts for a category
src/routes/timeline/+page.svelteArchive timeline

Build

Add Pagefind to your project build script:

package.json
{
  "scripts": {
    "build": "vite build && pagefind --site dist"
  }
}
txt
pnpm build
bash

The Pagefind step indexes the built site so the built-in search modal (⌘K / Ctrl+K) works. The current theme always renders this Pagefind integration, so keep the post-build indexing step enabled.

The resulting dist/ is a static bundle deployable to any static host.