Nextjsredirect() & notFound()

redirect() & notFound()

Besides Link and useRouter, Next.js provides two special functions for controlling navigation and 404 behavior directly from server-side code: redirect() and notFound(). Both work by throwing a special internal signal that Next.js catches to produce the right response.

redirect()

redirect(), imported from next/navigation, can be called inside Server Components, Server Actions, and Route Handlers to send the user to a different URL. By default it issues a temporary redirect.

TSX
// app/dashboard/page.tsx
import { redirect } from 'next/navigation'
import { getCurrentUser } from '@/lib/auth'

export default async function DashboardPage() {
  const user = await getCurrentUser()

  if (!user) {
    redirect('/login')
  }

  return <h1>Welcome, {user.name}</h1>
}
Warning
redirect() works by throwing internally, so calling it inside a try/catch block will be caught by your own catch clause unless you re-throw it. Avoid wrapping calls to redirect() in a try/catch that swallows errors.
redirect() in a Server Action

TSX
'use server'
import { redirect } from 'next/navigation'

export async function createPost(formData: FormData) {
  const title = formData.get('title') as string
  const post = await db.post.create({ data: { title } })

  redirect(`/blog/${post.id}`)
}

This is often simpler than fetching in a Client Component and calling router.push afterward — the redirect happens right where the mutation completes, on the server.

permanentRedirect()

When a URL has moved for good — say, a page was renamed and you want search engines and browsers to update their records — use permanentRedirect() instead. It issues a 308 status code rather than the 307 that redirect() uses.

Function

Status Code

Meaning

redirect()

307 (Temporary Redirect)

This URL points elsewhere for now; re-check it later.

permanentRedirect()

308 (Permanent Redirect)

This URL has permanently moved; update your bookmarks/links.

TSX
import { permanentRedirect } from 'next/navigation'

export default function OldBlogPage({ params }: { params: { slug: string } }) {
  permanentRedirect(`/articles/${params.slug}`)
}
notFound()

notFound(), also from next/navigation, tells Next.js to render the nearest not-found.tsx boundary and return a 404 status. It's the right tool whenever a requested resource genuinely doesn't exist — an invalid id, a deleted post, a slug with no match.

TSX
// app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation'
import { getPostBySlug } from '@/lib/posts'

export default async function BlogPostPage({ params }: { params: { slug: string } }) {
  const post = await getPostBySlug(params.slug)

  if (!post) {
    notFound()
  }

  return <article>{post.title}</article>
}

// app/blog/[slug]/not-found.tsx
export default function NotFound() {
  return <h1>That post could not be found.</h1>
}
Note
not-found.tsx is scoped like any other special file — a not-found.tsx placed in app/blog/[slug]/ only handles notFound() calls from within that segment. Without a local one, Next.js walks up to the nearest ancestor not-found.tsx, all the way to the root.
Config-Based Redirects

For simple, static redirects that don't depend on runtime data — like retiring an old marketing URL — it's often cleaner to declare them in next.config.js rather than adding logic to a page.

TSX
// next.config.js
module.exports = {
  async redirects() {
    return [
      {
        source: '/old-pricing',
        destination: '/pricing',
        permanent: true,
      },
    ]
  },
}
Choosing the Right Tool
  • Static, unconditional redirect known ahead of time -> next.config.js redirects().

  • Redirect depends on runtime data (auth state, form result, lookup) -> redirect() or permanentRedirect().

  • Resource genuinely does not exist -> notFound().

  • Redirect triggered by a user click in a Client Component -> useRouter().push()/replace() instead.

Tip
redirect() and notFound() only work in Server Components, Server Actions, and Route Handlers — they are not available in Client Components. Inside a Client Component, use the useRouter hook for navigation instead.

Together with Link and useRouter, redirect() and notFound() round out the App Router's navigation toolkit — giving you a way to control routing from server-side logic as naturally as you control it from the client.