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`
│ ├── env.ts the environment variables the app reads, see `vela env`
│ └── 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 config, adapter includedRoute 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.js'. 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. It is deliberately the canonical site, so a preview’s pages still point at production.
This file is ordinary code, and the one place the name lives. With a backend, PocketBase names the emails it sends after APP_NAME — {APP_NAME} in the verification and password reset templates — and vela dev and vela deploy hand it site.name through its environment, where it shows as locked in the admin panel. The URL those emails link to is separate from site.url: it is the origin each instance is served on, so a preview’s reset links hit the preview. See vela env for what else PocketBase reads from the environment.
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. .env.example does, and documents what the app reads.