App Structure
Layout of an app folder under apps/<name>/.
A scaffolded app is intentionally small. Almost every file is either configuration or a one-line re-export of a framework page. The only files that hold app-specific logic are backend/server.ts, frontend/app/providers.tsx, and anything you add under migrations/ and seeds/.
Tree
my-app/
app.config.json
license.lic # issued by Crudy, gitignored
license-public.pem # issued by Crudy
.env.example
Makefile # per-app docker compose wrappers
docker-compose.yml
docker-compose.dev.yml
backend/
server.ts
package.json
Dockerfile
Dockerfile.dev
tsconfig.json
frontend/
package.json
next.config.ts
proxy.ts
postcss.config.js
tsconfig.json
Dockerfile
Dockerfile.dev
app/
layout.tsx
page.tsx
providers.tsx
globals.css
login/page.tsx
[...slug]/page.tsx
migrations/
seeds/app.config.json
The single source of app metadata read by both halves.
{
"name": "my-app",
"title": "My App",
"audience": "my-app",
"description": "My App",
"fallbackRoute": "/",
"licenseFile": "./license.lic",
"licensePublicKeyFile": "./license-public.pem"
}audience must match the audience declared in your license. If they differ, the backend exits at boot. Always set the audience here, never from the environment. Send this value to Crudy when you request a license at /pricing.
backend/server.ts
The only place modules are named on the backend.
import { createCrudyServer } from '@crudy/backend';
import appConfig from '../app.config.json' with { type: 'json' };
await createCrudyServer({ modules: [], appConfig });Add a module by importing its backend half and listing it in the modules array:
import { documentsBackend } from '@crudy-modules/documents/backend';
await createCrudyServer({ modules: [documentsBackend], appConfig });frontend/app/providers.tsx
The only place modules are named on the frontend.
"use client";
import type { ReactNode } from "react";
import { CrudyProviders } from "@crudy/frontend";
import type { AppConfig } from "@crudy/frontend";
import { documentsFrontend } from "@crudy-modules/documents/frontend";
import rawConfig from "../../app.config.json";
const appConfig = rawConfig as AppConfig;
const MODULES = [documentsFrontend];
export function AppProviders({ children }: { children: ReactNode }) {
return (
<CrudyProviders modules={MODULES} appConfig={appConfig}>
{children}
</CrudyProviders>
);
}layout.tsx imports AppProviders and renders it under <html><body>. It only owns metadata and viewport.
frontend/app/page.tsx, login/page.tsx, [...slug]/page.tsx
Each is a one-line re-export of a framework page:
"use client";
export { HomePage as default } from '@crudy/frontend';[...slug]/page.tsx is the catch-all that resolves to a module-registered route by exact path. Module pages register through FrontendModule.routes, never by adding files under app/.
frontend/app/globals.css
A single line that imports the framework's compiled CSS:
@import "@crudy/frontend/globals.css";Modules that need their own globals add their own @import line here. Do not import Tailwind in your app's globals; each package owns its own.
frontend/proxy.ts
Configures the Next.js middleware that proxies API calls and gates routes.
import { createMiddleware } from '@crudy/frontend';
import rawConfig from '../app.config.json';
import type { AppConfig } from '@crudy/frontend';
export default createMiddleware(rawConfig as AppConfig);
export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] };license.lic and license-public.pem
The two license files Crudy sends you. Place them at the app root and the backend will pick them up via LICENSE_FILE and LICENSE_PUBLIC_KEY_FILE. See Licensing for the full lifecycle.
migrations/
Per-app SQL files applied after the core migrations. Use these for app-specific tables that no module owns.
seeds/dev.sql
Per-app dev fixtures. The backend re-applies them on a clean database. Do not use in production.