It begins as a Vite plugin that unlocks SSR, React Server Components, Server Functions, and realtime features.And it ends with you building something awesome!
export default defineApp([render(Document, [index(Home), // JSX pageroute("/users", UsersPage), // JSX pageroute("ping", () => new Response("pong", { status: 200 })),]),]);
defineApp([route("/upload/", async ({ request }) => {const formData = await request.formData();const file = formData.get("file") as File;// Stream the file directly to R2const r2ObjectKey = `/storage/${file.name}`;await env.R2.put(r2ObjectKey, file.stream(), {httpMetadata: {contentType: file.type,},});return new Response(JSON.stringify({ key: r2ObjectKey }), {status: 200,headers: {"Content-Type": "application/json",},});}),]);
const authRoutes = [route("/login", Login),route("/account", [async ({ ctx, request }) => {if (!ctx.user) {const headers = new Headers();await sessions.remove(request, headers);headers.set("Location", "/auth/login");return new Response(null, {status: 302,headers,});}},AccountPage]),];export default authRoutes;
function isAuthenticated({ request, ctx }) {// Ensure that this user is authenticatedif (!ctx.user) {return new Response("Unauthorized", { status: 401 })}}defineApp([route("/blog/:slug/edit", [isAuthenticated, EditBlogPage]);// EditBlogPage will only run if isAuthenticated = true])
defineApp([sessionMiddleware,async function getUserMiddleware({ request, ctx }) {if (ctx.session.userId) {ctx.user = await db.user.find({ where: { id: ctx.session.userId } });}},route("/hello", [function ({ ctx }) {if (!ctx.user) {return new Response("Unauthorized", { status: 401 });}},function ({ ctx }) {return new Response(`Hello ${ctx.user.username}!`);},]),]);
import { Document } from "@/pages/Document";import { NoJSDocument } from "@/pages/NoJSDocument";import { HomePage } from "@/pages/HomePage";export default defineApp([render(Document, [route("/", HomePage)]),render(NoJSDocument, [route("/no-js", () => new Response("No Javascript injected in this Document, just plain HTML",{ status: 200 }))])]);
React Server Components are a powerful way to build server-side web apps on Cloudflare.
Everything is server-first by default. Your components run on the server where they belong, streaming HTML straight to the browser. When you need interactivity, just mark a component with use client. It's the same mental model you'd use anywhere else—only now it runs on the edge.
There's no need to wrestle with bundlers, patch frameworks, or manually split code between server and client. RedwoodSDK treats React's directives as first-class citizens and integrates them seamlessly with Vite and Cloudflare Workers. The result is lightning-fast time-to-interactive, zero boilerplate, and a dev environment that mirrors production—without any extra setup.
RedwoodSDK is built for Cloudflare from the first line of code.
No adapters, no shims. What runs locally is what runs in production. Development uses Miniflare to emulate Cloudflare Workers with uncanny accuracy. You're not faking the edge. You're building on it. No config drift. No "it worked on my machine." Just a clean path from idea to deploy.
RedwoodSDK is for people who write software they own.
It's built on browser standards, not vendor abstractions—just native Web APIs and predictable behavior from request to response.
Bring your own tools. Use realtime out of the box. Run locally on Cloudflare's stack with zero config—D1, R2, Queues, Workers AI, and more.
No wrappers.No black boxes.Just flow.
Get a summary of what we've shipped, articles we've written, and upcoming events straight to your inbox, at most once every two weeks.