Send Clients to a Different URL With ctx.redirect()
Redirects
Use `ctx.redirect()` to issue 301 or 302 redirects, redirect back to the referrer, and generate safe redirect URLs from named routes with `router.url()`.
What you'll learn
- Issue a redirect with `ctx.redirect()`
- Choose between 301 permanent and 302 temporary redirects
- Generate route URLs with `router.url()` for safe redirects
A redirect tells the client to fetch a different URL. Koa makes this
simple with ctx.redirect(). The default status is 302 Found
(temporary); change it to 301 for permanent moves.
Basic Redirect
router.get("/old-path", async (ctx) => {
ctx.redirect("/new-path");
// status is 302 by default
}); ctx.redirect() sets the Location header and writes a short HTML
body so browsers that don’t follow redirects automatically show a
link.
301 Permanent Redirect
Set ctx.status before calling ctx.redirect():
router.get("/blog", async (ctx) => {
ctx.status = 301;
ctx.redirect("/posts");
}); Use 301 only when the old URL will never come back — search engines transfer link equity and browsers cache it permanently.
302 Temporary Redirect
302 is the safe default. Use it when the destination may change
(login redirects, A/B tests, maintenance pages):
router.get("/dashboard", async (ctx) => {
if (!ctx.session?.user) {
ctx.redirect("/login"); // 302
} else {
ctx.body = "welcome";
}
}); Redirect Back to Referrer
"back" redirects to the Referer header, with a fallback:
router.post("/logout", async (ctx) => {
ctx.session = null;
ctx.redirect("back", "/");
// goes to Referer, or "/" if header is absent
}); Generating URLs With router.url()
Hard-coding paths in redirects breaks when you rename a route.
Name your routes and use router.url() to generate the URL:
router.get("user-detail", "/users/:id", async (ctx) => {
ctx.body = { id: ctx.params.id };
});
router.post("/users", async (ctx) => {
const id = "42"; // newly created user ID
ctx.status = 201;
ctx.redirect(router.url("user-detail", { id }));
// redirects to /users/42
}); The first argument to router.get() (before the path) is the route
name. router.url(name, params) fills in the placeholders.
External Redirects
Pass a full URL to redirect outside your app:
router.get("/docs", async (ctx) => {
ctx.redirect("https://koajs.com");
}); Status Code Reference
| Code | Name | When to use |
|---|---|---|
| 301 | Moved Permanently | URL will never return |
| 302 | Found (temporary) | Temporary or conditional redirect |
| 303 | See Other | After POST — redirect to GET result |
| 307 | Temporary Redirect | Preserve HTTP method on redirect |
| 308 | Permanent Redirect | Preserve HTTP method, permanent |
Up Next
When no route matches at all, Koa needs a catch-all to return a proper 404 instead of an empty response.
404 Handling →