The symptom is usually one of these:
- the home page renders and then jumps away
- auth redirects flicker
-
redirect()works in one file but not another - a client-side button redirect gets copy-pasted into server code
The root cause is that Next.js has five redirect tools, and each one is for a different phase of the request.
The shortest correct answer is:
- use
redirect()for request-time server redirects - use
useRouter().push()in a client event handler - use
redirects()innext.config.jsfor static path rules
If / should always redirect, use next.config.js
This is the cleanest answer when the rule is fixed.
// next.config.mjs
const nextConfig = {
async redirects() {
return [
{
source: '/',
destination: '/dashboard',
permanent: false,
},
]
},
}
export default nextConfig
This happens before rendering, so there is no flash of the home page first.
If / redirects based on auth or data, use redirect()
In the App Router, this is the server-side pattern:
// app/page.tsx
import { redirect } from 'next/navigation'
export default async function HomePage() {
const isLoggedIn = false
if (isLoggedIn) {
redirect('/dashboard')
}
redirect('/login')
}
The current Next.js docs say redirect() returns a 307 by default, and returns 303 when used in a Server Action.
If the redirect happens after a button click, use useRouter()
This is for event handlers in client components, not for server rendering.
'use client'
import { useRouter } from 'next/navigation'
export default function GoToDashboardButton() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
If you do not need imperative navigation, keep using <Link />.
The mistake that causes most bad redirects
People often use the client pattern for a server problem:
// Avoid this for root-route redirect logic
'use client'
import { useEffect } from 'react'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
useEffect(() => {
router.push('/dashboard')
}, [router])
return null
}
This works, but it is the wrong tool for a route-level redirect. The user briefly loads /, then navigates away. Search crawlers and users both get a worse experience.
Another easy mistake: calling redirect() inside try
The docs explicitly note that redirect() throws. If you wrap it inside try/catch, you can accidentally swallow the navigation.
Use this shape instead:
import { redirect } from 'next/navigation'
export async function createProject() {
let id: string
try {
id = 'abc123'
} catch (error) {
throw error
}
redirect(`/projects/${id}`)
}
Which redirect API should you pick?
- Known path rule for every request:
redirects()innext.config.js - Request-time condition in server code:
redirect() - Canonical URL changed permanently:
permanentRedirect() - Click handler in client UI:
useRouter().push()
That mapping is the part most articles skip, and it is why the same redirect can feel broken in three different ways.
For adjacent routing gotchas, keep these open:
- Next.js App Router Guide: From Basics to Advanced Patterns
- Next.js 15 Middleware: Complete Guide to Auth, Rate Limiting, A/B Testing, and Edge Logic
- Next.js Server Actions vs API Routes: When to Use Each
- Fix Next.js revalidatePath Not Working in Server Actions
References
- Stack Overflow: Next.js Redirect from / to another page
- Next.js docs: Redirecting
- Next.js docs:
redirectsinnext.config.js
Related
Originally published at https://www.iloveblogs.blog
Top comments (0)