Redirects
Trailhead once put its trail pages under /hikes/…, and hundreds of old links still point there. Meanwhile, the saved-trails page must send signed-out visitors to the login screen. Both are redirects: answering a request with "not here, go there". They differ in whether the move is forever and in when you know it is needed.
The rule
Known ahead and forever: config, 308. Decided per request:
redirect(), 307.
A redirect is a response with a 3xx status and a Location header. The two you use in Next.js:
| Status | Meaning | Browsers and search engines |
|---|---|---|
| 307 Temporary Redirect | "Go there for now" | Keep the old URL |
| 308 Permanent Redirect | "It lives there now" | Update to the new URL |
Both keep the request method, so a POST stays a POST.
1. In next.config.ts, for moves you know before any request arrives:
// next.config.ts
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
async redirects() {
return [
{ source: "/hikes/:slug", destination: "/trails/:slug", permanent: true },
];
},
};
export default nextConfig;:slug carries the matching part across. permanent: true gives 308; false gives 307. These run before any page code.
2. In code, with redirect() from next/navigation, when the decision depends on the request. It works in server components, route handlers and server actions:
// app/saved/page.tsx
import { redirect } from "next/navigation";
import { getSession } from "@/lib/auth";
export default async function SavedPage() {
const session = await getSession();
if (!session) redirect("/login"); // 307; nothing below runs
return <SavedTrails hikerId={session.hikerId} />;
}redirect() works by throwing a special signal that Next.js catches, so code after it never runs. Do not wrap it in try/catch, or you may swallow the signal. For a permanent move decided in code, permanentRedirect() sends 308.
In a route handler you can also return NextResponse.redirect(new URL("/login", request.url)), which needs an absolute URL; building it from request.url handles that.
The shortcut
Ask two questions: "Do I know it before the request?" and "Is it forever?" Known ahead: config. Needs the request: redirect(). Forever: 308. For now: 307.
/hikes/:slug → /trails/:slug
Known ahead and forever. A config entry with
permanent: true: 308.
The trap is using a permanent redirect for something temporary. Browsers may cache a 308 and keep skipping the old URL even after you change your mind.
Drills
Answer fast — trust the rule.
A config redirect has permanent: true. Which status is sent?
Which fits "send signed-out visitors from /saved to /login"?
What happens to code written after redirect("/login") in a component?
In { source: "/hikes/:slug", destination: "/trails/:slug" }, where does /hikes/fox-hollow go?
Remember
- Known ahead and forever: a config redirect with
permanent: true(308). Decided per request:redirect()(307). - Shortcut: "known before the request?" and "is it forever?"
- Trap:
redirect()throws, so do not catch it, and never make a temporary move permanent.
