Skip to content

File Conventions

Cloudwerk uses a file-based routing system with specific file naming conventions. This reference documents all recognized file patterns.

These files define your application’s routes:

FilePurpose
page.tsxUI component for a route
route.tsAPI endpoint handlers
layout.tsxShared UI wrapper
middleware.tsRequest middleware
error.tsxError boundary
not-found.tsx404 handler
loading.tsxLoading state

Defines a publicly accessible page at the route path.

// app/about/page.tsx -> /about
import type { PageProps, LoaderArgs } from '@cloudwerk/core';
// Optional: Server-side data loading
export async function loader({ params, context }: LoaderArgs) {
return { data: await fetchData() };
}
// Required: Page component
export default function AboutPage({ data }: PageProps & { data: Data }) {
return <h1>About Us</h1>;
}

Exports:

  • default (required) - React component to render
  • loader (optional) - Server-side data fetching function

Defines API endpoints that handle HTTP methods.

// app/api/users/route.ts -> /api/users
import { json } from '@cloudwerk/core';
import type { CloudwerkHandlerContext } from '@cloudwerk/core';
export async function GET(request: Request, ctx: CloudwerkHandlerContext) {
return json({ users: [] });
}
export async function POST(request: Request, ctx: CloudwerkHandlerContext) {
const body = await request.json();
return json({ created: body }, { status: 201 });
}
export async function PUT(request: Request, ctx: CloudwerkHandlerContext) {
return json({ updated: true });
}
export async function PATCH(request: Request, ctx: CloudwerkHandlerContext) {
return json({ patched: true });
}
export async function DELETE(request: Request, ctx: CloudwerkHandlerContext) {
return new Response(null, { status: 204 });
}
export async function HEAD(request: Request, ctx: CloudwerkHandlerContext) {
return new Response(null, { status: 200 });
}
export async function OPTIONS(request: Request, ctx: CloudwerkHandlerContext) {
return new Response(null, {
headers: { 'Allow': 'GET, POST, PUT, DELETE, OPTIONS' }
});
}

Exports:

  • GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS - HTTP method handlers

Wraps pages and nested layouts with shared UI.

// app/layout.tsx
import type { LayoutProps, LoaderArgs } from '@cloudwerk/core';
// Optional: Layout-level data loading
export async function loader({ context }: LoaderArgs) {
const user = await context.auth.getUser();
return { user };
}
// Required: Layout component
export default function RootLayout({ children, user }: LayoutProps & { user: User }) {
return (
<html>
<head><title>My App</title></head>
<body>
<Header user={user} />
<main>{children}</main>
<Footer />
</body>
</html>
);
}

Exports:

  • default (required) - React component with children prop
  • loader (optional) - Server-side data fetching function

Runs before route handlers to modify requests/responses.

// app/middleware.ts
import type { Middleware } from '@cloudwerk/core';
export const middleware: Middleware = async (request, next, context) => {
// Before handling
console.log(`${request.method} ${request.url}`);
// Continue to route handler
const response = await next(request);
// After handling
response.headers.set('X-Custom', 'value');
return response;
};

Exports:

  • middleware (required) - Middleware function

Handles errors that occur in the route segment.

// app/error.tsx
'use client';
interface ErrorProps {
error: Error;
reset: () => void;
}
export default function ErrorPage({ error, reset }: ErrorProps) {
return (
<div>
<h1>Something went wrong</h1>
<p>{error.message}</p>
<button onClick={reset}>Try again</button>
</div>
);
}

Exports:

  • default (required) - React component with error and reset props

Handles 404 errors for the route segment.

// app/not-found.tsx
export default function NotFoundPage() {
return (
<div>
<h1>404 - Page Not Found</h1>
<a href="/">Go home</a>
</div>
);
}

Exports:

  • default (required) - React component

Displays while the page is loading (with streaming SSR).

// app/loading.tsx
export default function Loading() {
return <div className="spinner">Loading...</div>;
}

Exports:

  • default (required) - React component
app/users/[id]/page.tsx -> /users/:id

Access via params.id in loader:

export async function loader({ params }: LoaderArgs) {
const user = await getUser(params.id);
return { user };
}
app/docs/[...slug]/page.tsx -> /docs/*

Access via params.slug as an array:

export async function loader({ params }: LoaderArgs) {
// /docs/guides/routing -> params.slug = ['guides', 'routing']
const path = params.slug.join('/');
return { doc: await getDoc(path) };
}
app/shop/[[...categories]]/page.tsx

Matches:

  • /shop -> params.categories = undefined
  • /shop/electronics -> params.categories = ['electronics']
  • /shop/electronics/phones -> params.categories = ['electronics', 'phones']

Group routes without affecting the URL:

  • Directoryapp/
    • Directory(marketing)/
      • page.tsx # /
      • Directoryabout/
        • page.tsx # /about
    • Directory(dashboard)/
      • layout.tsx # Dashboard layout
      • Directorydashboard/
        • page.tsx # /dashboard

Render multiple pages simultaneously:

  • Directoryapp/
    • layout.tsx
    • Directory@sidebar/
      • page.tsx
    • Directory@main/
      • page.tsx

Access in layout:

export default function Layout({ sidebar, main }: LayoutProps) {
return (
<div className="flex">
<aside>{sidebar}</aside>
<main>{main}</main>
</div>
);
}

Intercept routes from parent segments:

ConventionBehavior
(.)Same level
(..)One level up
(...)Root level
  • Directoryapp/
    • Directoryfeed/
      • page.tsx
      • Directory(.)photo/
        • Directory[id]/
          • page.tsx # Intercepts /feed/photo/:id
    • Directoryphoto/
      • Directory[id]/
        • page.tsx # Direct access /photo/:id

Supported file extensions:

ExtensionDescription
.tsxTypeScript with JSX
.tsTypeScript
.jsxJavaScript with JSX
.jsJavaScript

These patterns are ignored by the router:

  • Files starting with _ (e.g., _components.tsx)
  • Files starting with . (e.g., .gitkeep)
  • Test files (*.test.ts, *.spec.ts)
  • Type declaration files (*.d.ts)
  • Non-route files (any file not matching route conventions)

You can colocate non-route files alongside routes:

  • Directoryapp/
    • Directoryusers/
      • page.tsx # Route
      • UserCard.tsx # Component (not a route)
      • user.utils.ts # Utility (not a route)
      • page.test.ts # Test (not a route)
      • _helpers.ts # Private (not a route)