---
title: "SvelteKit"
description: "Render Markdown pages in a SvelteKit app with Comark Content."
canonical_url: "https://content.comark.dev/integrations/sveltekit"
---
# SvelteKit

> Render Markdown pages in a SvelteKit app with Comark Content.

In SvelteKit, server `load` functions read the Content instance in-process and pass the page to the component. No endpoint is needed until the browser has to fetch content on its own. At the end of this guide you have a Markdown file rendered at `/`, edits showing up as you save, and a clear path to deployment.

You need a SvelteKit app. Create one with `npx sv create` if you're starting fresh.

::steps{level="3"}
### Install the packages

`comark-content` reads and parses your files. `@comark/svelte` renders the result.

  :::code-group
  ```bash [pnpm]
  pnpm add comark-content @comark/svelte
  ```

  ```bash [npm]
  npm install comark-content @comark/svelte
  ```

  ```bash [yarn]
  yarn add comark-content @comark/svelte
  ```

  ```bash [bun]
  bun add comark-content @comark/svelte
  ```
  :::

### Write a Markdown file

```md [content/index.md]
---
title: Home
---

# Hello from Markdown

This page is rendered by Comark Content.
```

### Create the Content instance

Put it under `$lib/server` so SvelteKit guarantees it never reaches the browser bundle:

```ts [src/lib/server/content.ts]
import { comarkContent } from 'comark-content'
import fs from 'comark-content/sources/fs'

export const content = comarkContent({
  source: fs('./content'),
})
```

### Render a page

A server `load` function reads the page and returns it as `data.page`:

```ts [src/routes/+page.server.ts]
import { error } from '@sveltejs/kit'
import { content } from '$lib/server/content'

export async function load() {
  const page = await content.get('/')
  if (!page) error(404, 'Page not found')

  return { page }
}
```

```svelte [src/routes/+page.svelte]
<script lang="ts">
  import { MarkdownDocument } from '@comark/svelte'

  let { data } = $props()
</script>

<MarkdownDocument value={data.page} />
```

Start the dev server and open <http://localhost:5173>:

```bash [Terminal]
pnpm dev
```

You see the heading and paragraph from `content/index.md`. `data.page.data.title` holds `Home`, ready for `<svelte:head>`.

Map Markdown elements and components to your own Svelte components through the `components` prop; see the [Svelte renderer](https://comark.dev/rendering/svelte).

### See edits while you develop

Edit `content/index.md` and reload: the text hasn't changed. The instance parsed the file once and keeps the result in memory. Start a watcher in development so saves update it:

```ts [src/hooks.server.ts]
import { dev } from '$app/environment'
import { content } from '$lib/server/content'

if (dev) {
  await content.watch()
}
```

Save the Markdown file again and reload; the change is there. [Watch content changes](https://content.comark.dev/guide/watch) explains what the watcher does.
::

You have a working setup. Everything below is optional; pick what you need.

## Render every page

A rest parameter route renders any path in your content folder. With `prerender` on, `entries` tells SvelteKit which paths to build from `list()`:

```ts [src/routes/[...path\\]/+page.server.ts]
import { error } from '@sveltejs/kit'
import { content } from '$lib/server/content'

export const prerender = true

export async function entries() {
  const pages = await content.list()
  return pages
    .filter((page) => page.path !== '/') // `/` is served by src/routes/+page.server.ts
    .map((page) => ({ path: page.path.slice(1) }))
}

export async function load({ params }) {
  const page = await content.get(`/${params.path}`)
  if (!page) error(404, 'Page not found')

  return { page }
}
```

Reuse the same `+page.svelte` as above. Drop `prerender` if you'd rather render on request.

## Generate types

Type `content.get()` from your frontmatter by generating source types:

```bash [Terminal]
npx comark-content prepare --config src/lib/server/content.ts
```

Add `comark-content.d.ts` to `.gitignore` and to your `tsconfig.json` `include`. See [Add TypeScript types](https://content.comark.dev/guide/typescript).

## Read from the browser

For client-side reads, such as instant search or navigation without a `load` round-trip, expose the instance on an endpoint and read it with the browser client. SvelteKit hands endpoints a web-standard `Request`, which is what [`content.handler()`](https://content.comark.dev/reference/content/handler) takes:

```ts [src/routes/api/content/[...path\\]/+server.ts]
import { content } from '$lib/server/content'

export const GET = ({ request }) => content.handler(request)
```

```ts [src/lib/content-client.ts]
import { createContentClient } from 'comark-content/client'

export const contentClient = createContentClient()
// contentClient.get('/'), contentClient.list(), contentClient.navigation()
```

The client has the same read methods as the server instance. [Client and handler](https://content.comark.dev/advanced/client-and-handler) explains how the two meet.

## Deploy

What you do depends on whether the production server can read `content/`:

- **`adapter-node` on a host that deploys your whole project** keeps working with `fs('./content')`. Start the server from the project root so the relative path resolves, or pass an absolute path. [Deploy with content files](https://content.comark.dev/deployment/with-content-files) has the checklist.
- **Serverless adapters** bundle only imported code, so `content/` isn't deployed. Parse it at build time and read the result at runtime. [Deploy with a content snapshot](https://content.comark.dev/deployment/with-a-snapshot#any-node-runtime) shows the pattern; include `.content/` in your deployment the way your adapter includes static files.
- **Content in a GitHub repository** is read at request time from any host. [Keep remote content up to date](https://content.comark.dev/deployment/remote-content) covers refreshing it.

## Going further

::card-group{cols="2"}
  :::card{icon="i-lucide-plug" title="Client and handler" to="https://content.comark.dev/advanced/client-and-handler"}
  How the handler and client pair works, and how plugins extend it.
  :::

  :::card{icon="i-lucide-list-tree" title="Build navigation" to="https://content.comark.dev/guide/navigation"}
  Turn your folders into a sidebar tree.
  :::
::


## Sitemap

See the full [sitemap](https://content.comark.dev/sitemap.md) for all pages.
