Skip to content

Cloudwerk uses a file-based routing system where your file structure defines your application’s routes. This guide covers everything from basic routes to advanced patterns.

Cloudwerk scans your app/ directory and compiles routes based on file conventions:

FileDescription
page.tsxUI for a route
route.tsAPI endpoint
layout.tsxShared UI wrapper
middleware.tsRequest middleware
error.tsxError boundary
not-found.tsx404 handler

Create a page.tsx file to define a route:

  • Directoryapp/
    • page.tsx # /
    • Directoryabout/
      • page.tsx # /about
    • Directorycontact/
      • page.tsx # /contact
// app/about/page.tsx
export default function AboutPage() {
return <h1>About Us</h1>;
}

Routes can be nested to any depth:

  • Directoryapp/
    • Directoryproducts/
      • page.tsx # /products
      • Directorycategories/
        • page.tsx # /products/categories
        • Directory[slug]/
          • page.tsx # /products/categories/:slug

Use brackets [param] for dynamic segments:

app/users/[id]/page.tsx -> /users/:id

Access the parameter in your loader:

export async function loader({ params }: LoaderArgs) {
const userId = params.id; // "123" for /users/123
return { user: await getUser(userId) };
}

Routes can have multiple dynamic segments:

app/[locale]/blog/[slug]/page.tsx -> /:locale/blog/:slug
export async function loader({ params }: LoaderArgs) {
const { locale, slug } = params;
return { post: await getPost(locale, slug) };
}

Use [...param] to match any number of segments:

app/docs/[...slug]/page.tsx
URLparams.slug
/docs/intro['intro']
/docs/guides/routing['guides', 'routing']
/docs/api/core/types['api', 'core', 'types']
export async function loader({ params }: LoaderArgs) {
const path = params.slug.join('/'); // "guides/routing"
return { doc: await getDoc(path) };
}

Use [[...param]] for optional catch-all segments:

app/shop/[[...categories]]/page.tsx
URLparams.categories
/shopundefined
/shop/electronics['electronics']
/shop/electronics/phones['electronics', 'phones']

Organize routes without affecting URLs using (groupName):

  • Directoryapp/
    • Directory(marketing)/
      • layout.tsx # Marketing layout
      • page.tsx # /
      • Directorypricing/
        • page.tsx # /pricing
    • Directory(dashboard)/
      • layout.tsx # Dashboard layout
      • Directorydashboard/
        • page.tsx # /dashboard
      • Directorysettings/
        • page.tsx # /settings

Use @slot directories for parallel route segments:

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

Access slots in your layout:

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

Intercept routes using (.), (..), or (...) prefixes:

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

Create API endpoints with route.ts:

// app/api/users/route.ts
import { json } 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 functions for the HTTP methods you want to handle:

  • GET - Read data
  • POST - Create data
  • PUT - Replace data
  • PATCH - Update data
  • DELETE - Remove data
  • HEAD - Headers only
  • OPTIONS - CORS preflight

When multiple routes could match, Cloudwerk uses this priority:

  1. Static routes (/about)
  2. Dynamic routes (/[id])
  3. Catch-all routes (/[...slug])