On this page

Route handlers

Create a route.ts file under app/ to expose an HTTP endpoint. A route handler is a server-side function that receives a standard Request  and returns a standard Response :

// app/api/hello/route.ts
export async function GET() {
  return Response.json({ message: "hello" })
}

export async function POST(request: Request) {
  const body = await request.json()
  return Response.json({ received: body }, { status: 201 })
}

A segment cannot contain both page.tsx and route.ts. If both would serve the same URL, the build fails.

Route handlers can have dynamic segments.

Methods and signature

Export one or more named HTTP method functions. Supported methods are GET, POST, PUT, PATCH, DELETE, HEAD, and OPTIONS.

Each method receives the request and a context object containing the matched route parameters. The handler must return a Response :

export async function GET(
  request: Request,
  { params }: { params: { id: string } },
) {
  const url = new URL(request.url)
  return Response.json({ id: params.id, tab: url.searchParams.get("tab") })
}

Static routes receive an empty params object. When a route has dynamic segments, type params with the names of those segments. Named params are strings. Catch-all params, such as [...slug], are arrays of strings.

Returning any value other than a Response results in a 500 response.

Cookies in Route Handlers

Use cookies(request) to read request cookies and cookies(response) to set response cookies:

import { cookies } from "@rebask/core/cookies"

export async function POST(request: Request) {
  const session = cookies(request).get("session")
  const response = Response.json({ authenticated: Boolean(session) })

  cookies(response).set("visited", "true", {
    httpOnly: true,
    path: "/",
  })

  return response
}

See cookies in the Server Runtime APIs docs for the available options.

Automatic methods and errors

  • HEAD runs an explicit HEAD handler when one exists. Otherwise, it runs GET, preserves its status and headers, and removes the response body.
  • OPTIONS runs an explicit OPTIONS handler when one exists. Otherwise, it returns 204 with an Allow header listing the implemented methods plus automatic HEAD and OPTIONS where applicable.
  • A supported method that is not exported returns 405.
  • An unrecognized method such as TRACE does not match a route handler and returns 404.

Caching and invalidation

Responses are not cached automatically. The Cache-Control header can be used to enable caching. Use s-maxage to define how long shared caches consider it fresh and stale-while-revalidate to allow stale responses while refreshing in the background:

export async function GET() {
  return Response.json(
    { products: await getProducts() },
    {
      headers: {
        "cache-control": "public, s-maxage=300, stale-while-revalidate=3600",
      },
    },
  )
}

Route handlers can also trigger cache invalidation. For example, a protected endpoint can call revalidateTag() to invalidate tagged server-side data:

import { revalidateTag } from "@rebask/core/cache"

export async function POST(request: Request) {
  const { tag } = await request.json()
  await revalidateTag(tag)
  return Response.json({ revalidated: tag })
}