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.