Skip to content

Quick Start

This guide walks you through building your first Cloudwerk application with pages, data loading, and API routes.

  1. Create a new page file at app/page.tsx:

    // app/page.tsx
    export default function HomePage() {
    return (
    <div>
    <h1>Welcome to Cloudwerk</h1>
    <p>Your full-stack framework for Cloudflare Workers.</p>
    </div>
    );
    }
  2. Start the dev server:

    Terminal window
    pnpm dev
  3. Open http://localhost:8787 to see your page.

Cloudwerk uses loader() functions for server-side data fetching:

// app/page.tsx
import type { PageProps, LoaderArgs } from '@cloudwerk/core';
export async function loader({ context }: LoaderArgs) {
// Access D1 database via Cloudflare bindings
const db = context.env.DB;
const { results: posts } = await db
.prepare('SELECT id, title, excerpt FROM posts ORDER BY created_at DESC LIMIT 10')
.all();
return { posts };
}
interface Props extends PageProps {
posts: Array<{ id: string; title: string; excerpt: string }>;
}
export default function HomePage({ posts }: Props) {
return (
<div>
<h1>Latest Posts</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<a href={`/posts/${post.id}`}>{post.title}</a>
<p>{post.excerpt}</p>
</li>
))}
</ul>
</div>
);
}

Use brackets [param] for dynamic route segments:

// app/posts/[id]/page.tsx
import type { PageProps, LoaderArgs } from '@cloudwerk/core';
import { NotFoundError } from '@cloudwerk/core';
export async function loader({ params, context }: LoaderArgs) {
const db = context.env.DB;
const post = await db
.prepare('SELECT id, title, content FROM posts WHERE id = ?')
.bind(params.id)
.first();
if (!post) {
throw new NotFoundError('Post not found');
}
return { post };
}
interface Props extends PageProps {
post: { id: string; title: string; content: string };
}
export default function PostPage({ post }: Props) {
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
</article>
);
}

Layouts wrap pages and persist across navigation:

// app/layout.tsx
import type { LayoutProps } from '@cloudwerk/core';
export default function RootLayout({ children }: LayoutProps) {
return (
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My Cloudwerk App</title>
</head>
<body>
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
</nav>
<main>{children}</main>
<footer>
<p>Built with Cloudwerk</p>
</footer>
</body>
</html>
);
}

API routes handle HTTP requests without rendering UI:

// app/api/posts/route.ts
import type { CloudwerkHandlerContext } from '@cloudwerk/core';
import { json, getContext } from '@cloudwerk/core';
export async function GET(request: Request, { params }: CloudwerkHandlerContext) {
const { env } = getContext();
const { results: posts } = await env.DB
.prepare('SELECT * FROM posts ORDER BY created_at DESC')
.all();
return json(posts);
}
export async function POST(request: Request, { params }: CloudwerkHandlerContext) {
const { env } = getContext();
const body = await request.json();
const id = crypto.randomUUID();
await env.DB
.prepare('INSERT INTO posts (id, title, content, created_at) VALUES (?, ?, ?, ?)')
.bind(id, body.title, body.content, new Date().toISOString())
.run();
return json({ id, title: body.title }, { status: 201 });
}

Middleware runs before route handlers:

// app/middleware.ts
import type { Middleware } from '@cloudwerk/core';
export const middleware: Middleware = async (request, next) => {
const start = Date.now();
// Run the route handler
const response = await next();
// Add timing header
const duration = Date.now() - start;
response.headers.set('X-Response-Time', `${duration}ms`);
return response;
};

Deploy your application globally:

Terminal window
pnpm deploy

Your app is now live on Cloudflare Workers!