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 } from '@cloudwerk/core'
import { db } from '@cloudwerk/core/bindings'
export async function loader() {
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 } from '@cloudwerk/core'
import { NotFoundError } from '@cloudwerk/core'
import { params } from '@cloudwerk/core/context'
import { db } from '@cloudwerk/core/bindings'
export async function loader() {
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 { DB } from '@cloudwerk/core/bindings'
import { json } from '@cloudwerk/core'
export async function GET() {
const { results: posts } = await DB
.prepare('SELECT * FROM posts ORDER BY created_at DESC')
.all()
return json(posts)
}
export async function POST(request: Request) {
const body = await request.json()
const id = crypto.randomUUID()
await 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!