Docs/Getting Started

Project structure and configuration

Veap does not force a rigid directory layout, but scaffolded applications and official plugins follow a predictable convention so the veap CLI, migrations, and virtual router work…

4 min read

Veap does not force a rigid directory layout, but scaffolded applications and official plugins follow a predictable convention so the veap CLI, migrations, and virtual router work together with zero boilerplate.

Typical directory layout#

A full-stack Next.js and Veap application is organized as follows:

text
my-app/
├── app/
│   ├── (site)/                  # Application-owned public routes and layouts
│   ├── [...catchAll]/
│   │   └── page.tsx             # Mounts @veap/plugins Virtual Router pages
│   ├── api/
│   │   └── [...catchAll]/
│   │       └── route.ts         # Mounts @veap/plugins Virtual Router API routes
│   ├── globals.css              # Tailwind CSS v4 + @veap/ui/globals.css
│   └── layout.tsx               # Root HTML shell, ThemeProvider, I18nProvider
├── components/                  # Application React components and layouts
├── lib/
│   ├── plugins.gen.ts           # Auto-generated plugin imports (managed by CLI)
│   └── veap.ts                  # Application composition root and initializeSystem()
├── locales/
│   ├── en.json                  # English ICU translation dictionary
│   └── de.json                  # German ICU translation dictionary
├── migrations/
│   └── index.ts                 # Application-level database migrations
├── plugins/                     # Local or ejected plugins (created by veap eject)
├── instrumentation.ts           # Next.js server startup hook
├── next.config.ts               # Next.js configuration
├── veap.config.ts               # Veap framework configuration
└── package.json

Framework configuration (veap.config.ts)#

Place a veap.config.ts file at the root of your project to configure framework-wide defaults such as the private admin path prefix and supported locales:

ts
// veap.config.ts
import type { VeapConfig } from "@veap/kernel";

const config: VeapConfig = {
  privatePath: "/app",
  intl: {
    cookie: "VEAP_LOCALE",
    default: "en",
    locales: ["en", "de"],
    timeZone: "UTC",
  },
  debug: false,
};

export default config;

Configuration properties#

VeapConfig supports the following settings:

PropertyTypeDefaultDescription
privatePathstring"/app"URL prefix used by panel and admin plugins for protected routes.
intl.cookiestring"NEXT_LOCALE"Cookie name used by @veap/intl to persist the active user locale.
intl.defaultstring"en"Fallback locale when no cookie or Accept-Language match is found.
intl.localesstring[]["en"]List of locale codes supported by the application.
intl.timeZonestring"UTC"Default IANA time zone for date and time formatting.
debugbooleanfalseEnables verbose diagnostic logging across kernel services.

Environment variables (.env)#

Runtime secrets and infrastructure connection strings belong in .env (or your deployment platform's environment settings) and are read through ConfigService:

ini
# Database connection (better-sqlite3, pg, or mysql2)
DB_CLIENT=better-sqlite3
DATABASE_URL=./data/app.sqlite

# Application URL
NEXT_PUBLIC_APP_URL=http://localhost:3000

# Mail transport (console, smtp, or custom)
MAIL_TRANSPORT=console
MAIL_FROM_ADDRESS=noreply@example.com

# Local file storage directory
FILE_STORAGE_FOLDER=public/uploads

Generated plugin registry (lib/plugins.gen.ts)#

When you install or remove plugins using the veap CLI (bun veap add <plugin> or bun veap remove <plugin>), Veap automatically updates lib/plugins.gen.ts:

ts
// lib/plugins.gen.ts
// This file is auto-generated by Veap CLI. Do not edit manually.
import type { IPlugin } from "@veap/plugins";
import authPlugin from "@veap/auth-plugin";
import panelPlugin from "@veap/panel-plugin";

export const plugins: IPlugin[] = [authPlugin, panelPlugin];

Import plugins in lib/veap.ts and pass it to PluginsServiceProvider and .withPlugins(plugins) so newly installed plugins register their routes, migrations, translations, and widgets automatically.

Catch-all routes for plugins#

If your application uses @veap/plugins to render virtual plugin pages (such as /signin from @veap/auth-plugin or /app from @veap/panel-plugin), mount the virtual router in app/[...catchAll]/page.tsx:

tsx
// app/[...catchAll]/page.tsx
import { buildRouteTree, VeapRouter } from "@veap/plugins/router";
import type { Metadata } from "next";
import { initializeSystem } from "@/lib/veap";

export const dynamic = "force-dynamic";

export default async function CatchAllPage({
  params,
  searchParams,
}: {
  params: Promise<{ catchAll?: string[] }>;
  searchParams: Promise<Record<string, string>>;
}) {
  const { catchAll } = await params;
  const resolvedSearchParams = await searchParams;
  const path = catchAll && catchAll.length > 0 ? `/${catchAll.join("/")}` : "/";

  await initializeSystem();
  const tree = await buildRouteTree(true);

  return VeapRouter({
    tree,
    path,
    searchParams: resolvedSearchParams,
  });
}

Because Next.js resolves explicit file-system routes in app/ before falling back to app/[...catchAll]/page.tsx, any physical page you create in app/ naturally overrides a plugin route at the same URL.

Customizing plugins with veap eject#

When you need full ownership over an installed plugin's UI, routes, or business logic, you can eject it into your application's local plugins/ directory:

bash
bun veap eject @veap/blog-plugin

This command clones the plugin's source repository into plugins/blog-plugin/, configures "workspace:*" in package.json, and links it via your package manager so you can customize every component and model locally without changing any package import specifiers.

Next steps#

Now that you know how a Veap project is structured, explore the @veap/kernel fundamentals:

veap
Preparing0%