Skip to content

Start

Install & adopt

Add the package. Register the Vite plugin before sveltekit(). Add the server handle. Then convert routes one at a time. The other Kit pages continue to operate.

On this page

ogygia installs into an applicable SvelteKit app. First, add the parts: the package, the Vite plugin, and the server handle. Then adopt it one route at a time. All other routes stay normal Kit pages.

Setup

The CLI is the fastest procedure. It installs the package. It registers the Vite plugin before sveltekit(). It adds the server handle and the transport. One command does all of this.

npx ogygia init
pnpm dlx ogygia init

To add markdown content collections at the same time, use npx ogygia init --markdown. If your project already exports a handle or a transport, the CLI merges into them. It puts your handle in a sequence and spreads into your transport. It does not overwrite them.

Manual setup

You can add the parts yourself. Install the package. Register the Vite plugin before sveltekit(). Add the server handle.

npm i ogygia
pnpm add ogygia

vite.config.ts

ogygia() must run before sveltekit(). The plugin also sets enforce: 'pre'. In monorepos, the plugin adds its package root to server.fs.allow in Vite. Thus Vite does not block shim and runtime resolves outside the app directory. For each option, see the API reference.

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. render: 'deferred' and render: 'live' use this endpoint. If you have other handles, put them together with sequence(). The default path is /__ogygia__.

To change the path, use ogygia.handle({ endpoint: '/my-islands' }). The handle then answers there, and every hole on your pages asks there. Pick a path your deployment 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/…): 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 / form POSTs go through the CSRF protection of Kit. Thus, set ORIGIN on your deployment. Without it, Kit rejects those POSTs. This is one env var. You set it one time.

TypeScript types

ogygia resolves some build-time virtual modules (virtual:ogygia/*). Your editor and svelte-check need one reference line to see their types. Add a src/ogygia.d.ts:

/// <reference types="ogygia/types" />

That is the full file. Without it, svelte-check shows the virtual imports as unresolved. The build operates correctly in each case. npx ogygia init writes this file for you.

Transport (held regions over the wire)

Held regions render inline with no setup. But some held regions cross the wire. Examples: a search remote, or a live content source. Those need the transport hook. Install it one time in your universal hooks:

import * as ogygia from 'ogygia';
export const transport = { ...ogygia.transport };

Markdown, Shiki & mdsvex (optional)

For .svx / .md content collections, add the optional peers. Then set markdown on under the same ogygia() surface. There is no separate plugin:

pnpm add -D mdsvex shiki
import { sveltekit } from '@sveltejs/kit/vite';
import { ogygia } from 'ogygia/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. Do not put them inline on sveltekit(). Kit v2, svelte-check, and the Svelte language server read svelte.config.js. If you pass them inline, Kit ignores svelte.config.js, and svelte-check does not see them:

import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
import { ogygia } from 'ogygia/vite';

export default {
  extensions: ogygia.extensions(), // includes .svelte, adds .svx / .md
  preprocess: [vitePreprocess(), ...ogygia.preprocess()]
};

mdsvex and shiki stay optional peers. ogygia.preprocess() is synchronous. When markdown is off, it does not load mdsvex. mdsvex loads at first use. If mdsvex is absent, you get an error with an install hint. ogygia.extensions() adds .svx / .md only when markdown is on. Set themes and headings under content: { markdown }. See content collections.

Adopt one route at a time

ogygia is incremental. Do not convert the full app. Set islands on one route at a time. All other routes stay normal SvelteKit pages.

When to use which mode

A route that is almost static: use csr = false plus islands. This gives the largest gain. A route that is a full interactive app: keep it csr = true. You can still put an island in it. A full site of static-first pages: use all-islands mode. You do not select one mode for the full app.

One route

Remove the Kit client from a single route and mark its islands. The other routes do not change. For the effect of csr = false, and why islands operate without the Kit client, see the Overview.

export const csr = false;

The router is global

The SPA router is on by default, for the full app. There is no component to render. On routes with csr = true, the Kit client owns navigation. ogygia does not intercept clicks there. Thus the two operate together. To remove the router, use ogygia({ router: false }). See Router.

Islands on a normal Kit page

An island does not need csr = false. Marked imports operate on a csr = true Kit page also. The component becomes an island. The remainder of the page hydrates the Kit way. What such a page ships is decided by what it rendered, not by what it imports: a marked component’s client wrapper loads its component only when the server rendered that island (a stamp in the head says so), so a registry of many marked blocks imported by a csr = true route 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 full app. If each route has csr = false, Kit usually skips the client build. ogygia finds this condition and runs a standalone islands build. Thus your island chunks continue to ship.

You do not need a keepalive route. When Kit would skip its client build, the Vite plugin injects a hidden route with no URL. The route exists only during the build. The plugin then removes it. Islands operate with zero placeholder files in your project.

Do / don’t

DoDon’t
Start with your most static route that has the most traffic. There, the removal of the Kit client gives the largest gain.Do not set the full 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'.Do not add a placeholder csr = true page to keep an all-islands build alive. It is not necessary.
Keep true app-like routes on csr = true. ogygia operates together with them.Do not forget ORIGIN in production. Server-island and form POSTs need it.