Deploy a SvelteKit app
SvelteKit is the one framework in this list where a brand new project will not deploy as-is. Not because of anything Lucity does, but because the scaffold ships with adapter-auto, which produces nothing runnable outside the four hosts it knows about. Two small changes fix it permanently.
The two changes
npx sv create my-app
cd my-app
npm install -D @sveltejs/adapter-node
Then swap the adapter. In current SvelteKit the adapter lives in vite.config.ts, not in svelte.config.js as older guides say:
// vite.config.ts
import adapter from '@sveltejs/adapter-node';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [sveltekit({ adapter: adapter() })],
});
And add a start script, because the adapter writes a server to build/ but nothing runs it for you:
{
"scripts": {
"start": "node build"
}
}
That is the whole setup. zeitlos/sveltekit-example(opens in a new tab) is a minimal project with exactly these changes applied.
Why it fails without them
Worth understanding, because the failure is quiet. With adapter-auto and no start script, the build succeeds: npm run build runs, files are produced, the image is pushed. Detection then looks for a way to start the app, finds no start script, no main field and no index.js, and derives an empty start command. The deployment has nothing to run.
You get a service that built cleanly and never becomes healthy, which is a more confusing outcome than a failed build. If you see that, this is almost always why.
With the two changes above, detection resolves the start command to npm run start, and adapter-node reads the PORT Lucity provides without further configuration.
Environment variables
SvelteKit is strict about the difference, and the strictness is useful:
$env/static/privateis inlined at build time. Changing one of these values needs a rebuild.$env/dynamic/privateis read at runtime fromprocess.env. Changing one of these takes effect on the next rollout, in seconds.- The
PUBLIC_prefixed variants of both are exposed to the browser.
For anything that comes from a Lucity dynamic variable, a database URL or bucket credentials, use $env/dynamic/private. Those values can change without your code changing, and a build-time import would freeze whatever was true when the image was made.
// src/routes/+page.server.ts
import { env } from '$env/dynamic/private';
import { Pool } from 'pg';
const pool = new Pool({ connectionString: env.DATABASE_URL });
export async function load() {
const { rows } = await pool.query('SELECT title FROM posts ORDER BY votes DESC LIMIT 10');
return { posts: rows };
}
Prerendered routes need their data at build time
If a route is prerendered and it queries a database, that database has to be reachable while the build runs, not just at runtime. Either drop the prerender on those routes or load their data from a source available during the build.