Start
Install & adopt
Add the package, register the Vite plugin before sveltekit(), and add the server handle. Then convert routes one at a time while the rest of your Kit pages keep working as they are.
On this page
ogygia installs into an existing SvelteKit app. You add three parts once (the package, the Vite plugin and the server handle) and then adopt ogygia one route at a time, so every route you have not converted stays a normal Kit page.
Setup
The CLI does the whole setup in one command: it installs the package, registers the Vite plugin before sveltekit(), and adds the server handle and the transport.
npx ogygia initpnpm dlx ogygia initTo add markdown content collections at the same time, run npx ogygia init --markdown. If your project already exports a handle or a transport, the CLI merges into them instead of overwriting them: it puts your handle in a sequence and spreads into your transport.
The command adds ogygia to your dependencies, plus @ogygia/content with --markdown, and adds @ogygia/cli to your devDependencies so that later npx ogygia commands run your local copy. It installs none of the other feature packages; you add those yourself, as described in Other features. The CLI reference lists every command.
Manual setup
If you prefer to add the parts yourself, install the package, register the Vite plugin before sveltekit(), and add the server handle.
npm i ogygiapnpm add ogygiavite.config.ts
ogygia() must run before sveltekit(), and the plugin also sets enforce: 'pre'. In a monorepo, the plugin adds its package root to Vite’s server.fs.allow, so Vite does not block the shims and runtime it resolves from outside the app directory. The API reference covers each option.
import { sveltekit } from '@sveltejs/kit/vite';
import { ogygia } from 'ogygia/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [ogygia(), sveltekit()] // order matters
});hooks.server.ts
ogygia.handle() serves the signed island endpoint that render: 'deferred' and render: 'live' regions fetch from. Its default path is /__ogygia__. If you have other handles, combine them with sequence().
To change the path, pass ogygia.handle({ endpoint: '/my-islands' }). The handle then answers there, and every hole on your pages asks there. Pick a path your deployment actually sends to the app: behind a proxy that only forwards some prefixes, put it under one of them (for example /_server/api/__ogygia__). Don’t put it under Kit’s appDir (/_app/…), because Kit answers those paths itself before any handle runs.
import * as ogygia from 'ogygia/server';
export const handle = ogygia.handle();In production
Server islands and command or form POSTs go through Kit’s CSRF protection, so set ORIGIN on your deployment. Without it, Kit rejects those POSTs.
TypeScript types
ogygia resolves some build-time virtual modules (virtual:ogygia/*), and your editor and svelte-check need one reference line to see their types. Add a src/ogygia.d.ts that contains only this:
/// <reference types="ogygia/types" />Without it, svelte-check reports the virtual imports as unresolved, although the build works either way. npx ogygia init writes this file for you.
Transport (held regions over the wire)
Held regions that render inline need no setup. Some held regions cross the wire, though, such as one returned from a search remote or a live content source, and those need the transport hook. Install it once in your universal hooks:
import * as ogygia from 'ogygia';
export const transport = { ...ogygia.transport };Markdown, Shiki & mdsvex (optional)
For .svx and .md content collections, add the content package, which brings mdsvex and Shiki, and list its content() plugin beside ogygia():
pnpm add @ogygia/contentimport { sveltekit } from '@sveltejs/kit/vite';
import { ogygia } from 'ogygia/vite';
import { content } from '@ogygia/content/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
ogygia(),
content({ markdown: {} }), // {} = defaults: Shiki + heading ids + collected headings
sveltekit()
]
});Put extensions and preprocess in svelte.config.js, not inline on sveltekit(). Kit v2, svelte-check and the Svelte language server all read svelte.config.js, and if you pass the options inline, Kit ignores svelte.config.js and svelte-check never sees them:
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { preprocess, extensions } from '@ogygia/content/vite';
export default {
extensions: extensions(), // includes .svelte, adds .svx / .md
preprocess: [vitePreprocess(), ...preprocess()]
};preprocess() is synchronous and does not load mdsvex while markdown is off; mdsvex loads on first use. extensions() adds .svx and .md only when markdown is on. Themes and headings are set under content({ markdown }), which content collections describes.
The content macros (import.meta.og.loader, og.code, og.md) bring their own types, so add a second line to src/ogygia.d.ts:
/// <reference types="ogygia/types" />
/// <reference types="@ogygia/content/types" />Other features (optional)
The other features are set up the same way as markdown. Each is a package you install, with a plugin you list once beside ogygia() and before sveltekit(), for example plugins: [ogygia(), flags(), sveltekit()]:
@ogygia/flags(flags()): kill switches, rollouts and A/B tests, decided on the server. See Flags & experiments.@ogygia/postrender(postrender()): pages stored whole on their first request and served as bytes. See Postrender.@ogygia/federation(federation()): other teams’ apps rendered into your pages. See Fragment federation.@ogygia/router(router()): routes declared as values in code. See Server router.@ogygia/devtools(devtools()): the in-page devtools dock and the SSR profiler. See Devtools and Profiler.@ogygia/testing(no plugin): Playwright helpers. See Testing.
Some of these also need a line in hooks.server.ts, such as postrender’s handle or the router’s. Plugins gives each package’s install command, Vite line and server wiring.
Adopt one route at a time
You don’t need to convert the whole app. ogygia is incremental, so you can put islands on one route and leave every other route a normal SvelteKit page. A route that is almost static gains the most from csr = false plus islands. A route that is a full interactive app can stay csr = true and still use an island where it helps. A site made entirely of static-first pages can go all-islands. You choose per route, not once for the whole app.
One route
To convert a route, remove the Kit client from it and mark its islands. The other routes do not change. The Overview explains what csr = false does and why islands work without the Kit client.
export const csr = false;The router is global
The SPA router is on by default for the whole app, and there is no component to render. On routes with csr = true, the Kit client owns navigation and ogygia does not intercept clicks there, so the two coexist. To turn the router off, use ogygia({ router: false }). See SPA router.
Islands on a normal Kit page
An island does not need csr = false. Marked imports work on a csr = true Kit page as well: the component becomes an island and the rest of the page hydrates the Kit way. What such a page ships depends on what it rendered, not on what it imports. A marked component’s client wrapper loads its component only when the server rendered that island (a stamp in the head records this), so a csr = true route that imports a registry of many marked blocks links the stylesheet and chunk of the blocks on the page, not of every block in the registry. A block that first appears after a client-side navigation loads on demand.
All-islands apps
You can also remove the Kit client from the whole app. When every route has csr = false, Kit usually skips its client build, so ogygia detects this and runs a standalone islands build to keep your island chunks shipping.
You do not need a keepalive route for this. When Kit would skip its client build, the Vite plugin injects a hidden route with no URL that exists only during the build and is removed afterwards, so islands work without any placeholder files in your project.
Do / don’t
| Do | Don’t |
|---|---|
| Start with your most static, highest-traffic route, where removing the Kit client gains the most. | Don’t set the whole app to csr = false on day one. Convert route by route and measure. |
Register ogygia() before sveltekit(). Order matters, and the plugin sets enforce: 'pre'. | Don’t add a placeholder csr = true page to keep an all-islands build alive. It is not needed. |
Keep truly app-like routes on csr = true. ogygia works alongside them. | Don’t forget ORIGIN in production. Server-island and form POSTs need it. |