Project Structure

A Vela project is an ordinary SvelteKit project. Everything below is a real file you own and can edit — there is no runtime and no hidden framework code.

my-project/
├── data/                  the PocketBase backend
│   ├── fixtures/          mock data for development and tests
│   ├── hooks/             PocketBase JS hooks and cron jobs
│   └── seeds/             data the application needs to function
├── migrations/            database migrations, applied in order
├── src/
│   ├── lib/
│   │   ├── components/ui/ shadcn-svelte components
│   │   ├── schemas/       Zod schemas, generated alongside models
│   │   ├── server/        server-only code, including the workflow runtime
│   │   ├── workflows/     background workflows, one file each
│   │   ├── site.ts        the app's name and public URL
│   │   └── utils.ts       the cn helper and component type utilities
│   ├── routes/
│   │   ├── (public)/      routes anyone can reach
│   │   ├── (app)/         routes behind authentication
│   │   └── api/           your own API routes
│   ├── app.css            Tailwind imports and theme tokens
│   ├── app.d.ts           the App.Locals types, kept in sync by `vela sync`
│   └── hooks.server.ts    the handlePocketbase middleware and the workflow worker's init
├── static/                served as-is
├── test/setup.ts          the server test context
├── .vela/project.json     the app id and target bindings — commit this
├── components.json        shadcn-svelte configuration
├── vite.config.ts         Vite, Tailwind and the SvelteKit adapter
└── velastack.config.ts    deploy defaults (optional)

Route groups

Vela leans on SvelteKit’s route groups to express access. (public) is reachable by anyone; (app) is created when auth is enabled and requires a signed-in user, with unauthenticated requests redirected to the login page. The generators place new routes in (app) when auth is enabled and (public) when it isn’t, and --route overrides that. In a project with neither group, they go directly in src/routes.

App name and URL

src/lib/site.ts holds the app’s name and the URL it is served on, in every project, with a backend or without:

export const site = {
    name: 'My App',
    url: 'http://localhost:5173'
};

Pages read it with import { site } from '$lib/site'. vela create writes the name. Set url to where the site is deployed: canonical links, Open Graph image URLs and the blog’s RSS feed are built from it, prerendered pages included. vela deploy warns while it is still a localhost address or doesn’t match the domain being deployed to.

This file is the source of truth for both. With a backend, PocketBase keeps an application name of its own for the emails it sends — {APP_NAME} in the verification and password reset emails — so vela dev and vela deploy copy site.name into it every time they run. A name changed in the admin panel is overwritten the next time either runs; change src/lib/site.ts instead.

Workflows

src/lib/workflows/ holds one file per background workflow, and src/lib/server/workflows.ts the client and worker they run on. The worker starts from the init hook in src/hooks.server.ts and does nothing until there is a workflow file to run.

Generated and synced

src/app.d.ts and the collection types are regenerated by vela sync, which runs automatically after database changes in development. src/lib/schemas is written by the generators and is yours to edit afterwards.

Committed state

.vela/project.json holds the permanent app id and the server each target is bound to. It belongs in version control — without it, a CI deploy mints a new id and orphans the app already on the server. .env does not: it holds the local superuser credentials, and production values live on the server, managed with vela env.