Request Handling & Routing
RedwoodSDK’s routing system is built around the request/response cycle.
Each incoming Request
is processed and generates a corresponding Response
.
Routes are registered in defineApp
, where incoming request URLs are matched to route functions that return Response
objects.
import { defineApp } from '@redwoodjs/sdk/worker'import { route } from '@redwoodjs/sdk/router'
export default defineApp({ route('/', function() { return new Response('Hello, world!') })})
URL Pattern Matching
RedwoodSDK supports three URL matching patterns:
/static/
: Exact path matching. While trailing slashes are optional in the URL, they are always present in the internal route./params/:id/:name/
: Parameter matching using colons (:
). Named parameters are accessible in the request function viaparams.id
andparams.name
./wildcard/*
: Wildcard matching captures everything after the specified path. For example,/wildcard/uploads/images/avatar.png
makes"uploads/images/avatar.png"
available asparams.$0
.
Note: Regular expressions and type casting are not supported.
Route functions
Route functions handle incoming requests and return either a Response object or a JSX component. They receive the following properties:
request
: The incoming Request objectparams
: URL parameters parsed from the route definitionenv
: CloudFlare environment variables and bindingsctx
: A mutable object for storing request-scoped data
Here’s a basic example:
import { route } from "@redwoodjs/sdk/router";
route("/", function ({ request, params, env, ctx }) {return new Response("Hello, world!", { status: 200 });});
When returning a JSX component, RedwoodSDK will:
- Render it to static HTML
- Hydrate it on the client side using React
For more details on JSX handling, see the JSX section below.
Interruptors
RedwoodSDK’s interruptors allow you to chain multiple request functions for a single route, where each function can either:
- Return a Response to interrupt the chain
- Return nothing to continue to the next function
- Return a JSX component as the final response
This pattern is particularly useful for implementing middleware-like functionality at the route level, such as authentication checks.
Here’s an example:
import { defineApp } from "@redwoodjs/sdk/worker";import { route } from "@redwoodjs/sdk/router";
export default defineApp([route("/user/settings", [ // Authentication check function checkAuth({ ctx }) { if (!ctx.user) { return Response.redirect("/user/login", 302); } }, // Main page request function showSettings() { return <UserSettingsPage />; },]),]);
The interruptors pattern complements the global middleware system, allowing you to apply route-specific checks without cluttering your main request functions.
Note: All request functions in the chain receive the same route context (request
, params
, env
, ctx
), making it easy to share data between functions.
Global Middleware
Global middleware allows you to inspect or modify every request and response in your application. Middleware functions are defined in the defineApp
function and run in sequence before any route handling. You can specify as many middleware functions as needed.
export default defineApp([async function getUserMiddleware({ request, env, ctx }) { const session = getSession({ request, env }) try { const user = await db.user.findFirstOrThrow({ select: { id: true, email: true }, where: { id: session?.userId } }) ctx.user = user } catch { ctx.user = null }},route('/', function({ ctx }) { return new Response(`You are logged in as "${ctx.user.email}"`)})])
In this example, we define a global middleware function getUserMiddleware
that:
- Retrieves the user’s session information
- Attempts to fetch the user from the database using the session’s userId
- Stores the user object in the request context (
ctx
)
The ctx
object is then available to all subsequent route handlers and middleware functions.
JSX
Route functions can return either a Response
object or a JSX component. When returning JSX, RedwoodSDK:
- Renders it as static HTML
- Includes an RSC (React Server Components) payload
- Hydrates the component on the client side
By default, all components use the "use server"
directive. For interactive components, explicitly add the "use client"
directive.
Basic Example
import { defineApp } from "@redwoodjs/sdk/worker";import { route } from "@redwoodjs/sdk/router";
function Homepage() {return <div>Hello, world! I'm a React Server Component!</div>;}
export default defineApp([route("/", Homepage)]);
This renders as simple HTML:
<div>Hello, world! I'm a React Server Component!</div>
Root Document
By default, RedwoodSDK only renders your component’s HTML without <html>
, <head>
, or <body>
tags. To add these, wrap your routes in a layout:
import { defineApp } from "@redwoodjs/sdk/worker";import { route, layout } from "@redwoodjs/sdk/router";
function Document({ children }) {return (<html><head><title>RedwoodSDK App</title></head><body>{children}</body></html>);}
export default defineApp([layout(Document, [ route("/", Homepage)])]);
Adding Interactivity
To enable client-side hydration:
- Create a client entry point:
import { initClient } from "@redwoodjs/sdk/client";
initClient();
- Include it in your Document layout:
function Document({ children }) {return ( <html> <head> <title>RedwoodSDK App</title> <script type="module" src="/src/client.tsx" /> </head> <body>{children}</body> </html>);}
Helpers
The following helpers improve the development experience of routing:
prefix
allows you to prepend a string to an array of routesindex
is a shorthand for defining the root route (/
) of a section
Here’s an example using both helpers:
import { defineApp } from "@redwoodjs/sdk/worker";import { index, layout, prefix } from "@redwoodjs/sdk/router";import { authRoutes } from 'src/pages/auth/routes';import { invoiceRoutes } from 'src/pages/invoice/routes';import HomePage from 'src/pages/Home/HomePage';
export default defineApp([layout(Document, [ // Define root route using index index([ HomePage, ]), // Prefix routes with /user and /invoice prefix("/user", authRoutes), prefix("/invoice", invoiceRoutes),])])
The index
helper is equivalent to calling route('/', handler)
, making it more semantic when defining root routes for your application or route groups.