Lessons
What it actually took to wire SvelteKit 3 RC + Vite+ + Cloudflare together. Useful if you're extending this — human or agent.
SvelteKit 3 (RC)
- No
svelte.config.js. Adapter and compiler options are passed inline tosveltekit()invite.config.ts. $libis gone — it's#lib, apackage.jsonimportssubpath. An extensionless#lib/thingdoesn't resolve inside.sveltefiles, so re-export everything through asrc/lib/index.tsbarrel and import#lib. Components and assets (which have extensions) can be imported directly:#lib/components/X.svelte.$app/tsconfigis virtual —svelte-kit syncwrites it tonode_modules/$app/tsconfig.json. Anything that readstsconfig.jsonoutside the Vite pipeline (vp check,svelte-check) needs asvelte-kit syncfirst, hence"check": "svelte-kit sync && vp check".- The whole site is static, so
src/routes/+layout.tsjust doesexport const prerender = true.
Vite+
- No global
vpneeded. It's a dev dependency; thepackage.jsonscripts call it and pnpm resolves it fromnode_modules/.bin, so teammates only need Node and pnpm. (VoidZero's own templates assume a globalvp— that also works, it's just not required.) - Config lives in
vite.config.ts, infmt/lint/test/checkblocks — not.oxfmtrc/.oxlintrc. UsedefineConfigfromvite-plus. - Deduplicate Vite. SvelteKit's peers pull a real
vite, while Vite+ wants@voidzero-dev/vite-plus-core. Left alone you get two. Fix: apnpm-workspace.yamloverride —'vite@*': 'npm:@voidzero-dev/vite-plus-core@<v>'— plus keepviteas a dev dependency alias so pnpm has the edge. This is whatvp migratedoes. - Tests import from
vite-plus/test, notvitest(not a direct dep) and not@voidzero-dev/vite-plus-test(removed in 0.3.x). You can drop thevitestdependency entirely. vp checkruns the tools directly, not through Vite — so it won't runsvelte-kit syncfor you.- Guard the SvelteKit plugin out of Vitest (
process.env.VITEST) or you hit "The configured Vite SSR environment must be a RunnableDevEnvironment".
pnpm 12
- Settings moved from
.npmrc/package.json#pnpmtopnpm-workspace.yaml. onlyBuiltDependenciesis now anallowBuilds:map (esbuild: true, …).minimumReleaseAgeblocks packages published in the last N minutes — a supply-chain guard. Set0to track newest; a real project wants1440+.- On an Intel Mac, mise can't install pnpm 12 from the default (aqua) backend —
no
darwin-x64build. Use"github:pnpm/pnpm".
Content
- Chose
import.meta.glob('/src/content/*.md', { query: '?raw', eager: true })+marked+ a/^#\s+(.+)$/mtitle regex over Content Collections: no config file, no codegen step, no sync ordering. ~30 lines insrc/lib/docs.ts. - These docs carry no frontmatter — the title comes from the H1, and nav
order + card copy live in one
NAVarray. So the same file reads cleanly on GitHub and renders on the site. - If you do want frontmatter:
gray-matterpulls a transitive directeval(Rolldown warns). Usejs-yaml'sloadon the---block yourself instead. - Rendering happens at build time (pages are prerendered), so
markednever reaches the client or the Worker.
Cloudflare
@sveltejs/adapter-cloudflarewrites.svelte-kit/cloudflare;wrangler.jsoncmain+assets.directorypoint there.worker-configuration.d.ts(fromwrangler types) is committed sovp checkis self-contained — re-runpnpm genafter editingwrangler.jsonc.
CI
pnpm/action-setup+actions/setup-node(node-version-file: .node-version,cache: pnpm) is all the setup needed. Every step runs throughpnpm run …, so no global tooling in CI either.