Complete Guide to Back Button Navigation in Next.js

Learn the different approaches to implement back button navigation in Next.js, from simple router.back() to fallback strategies for direct page access.

BySourav Mishra

Back button navigation sounds simple, but in Next.js there are nuances you need to understand to get it right.

The Core Methods

Next.js provides two main approaches for programmatic navigation:

1. Using router.back()

The most common approach uses the useRouter hook from next/navigation:

"use client";

import { useRouter } from "next/navigation";

export const BackButton = () => {
    const router = useRouter();

    return (
        <button onClick={() => router.back()}>
            Go Back
        </button>
    );
};

Key points:

  • Requires "use client" directive since hooks only work in client components
  • Uses browser's history stack, just like clicking the browser's back button
  • If there's no history (user opened page directly), it does nothing

2. Using window.history.back()

For simple cases without needing the router:

"use client";

export const BackButton = () => {
    return (
        <button onClick={() => window.history.back()}>
            Go Back
        </button>
    );
};

This is functionally equivalent to router.back() but doesn't require importing the router.

The Direct Access Problem

What happens when someone opens your blog post directly from a search result? There's no history to go back to.

Solution: Fallback Navigation

"use client";

import { useRouter } from "next/navigation";

type BackButtonProps = {
    fallbackUrl?: string;
};

export const BackButton = ({ fallbackUrl = "/" }: BackButtonProps) => {
    const router = useRouter();

    const handleBack = () => {
        // Check if there's history to go back to
        if (window.history.length > 1) {
            router.back();
        } else {
            router.push(fallbackUrl);
        }
    };

    return (
        <button onClick={handleBack}>
            Go Back
        </button>
    );
};

Note: window.history.length > 1 isn't foolproof—some browsers initialize with a length of 1 or 2. For critical flows, consider tracking navigation state yourself.

Server vs Client Components

Since useRouter requires client components, you have two patterns:

Pattern A: Dedicated Client Component

Keep your page as a server component, create a small client component just for the button:

// components/back-button.tsx
"use client";

import { useRouter } from "next/navigation";

export const BackButton = ({ label }: { label: string }) => {
    const router = useRouter();
    return (
        <button onClick={() => router.back()}>
            {label}
        </button>
    );
};

// app/blog/[slug]/page.tsx (server component)
import { BackButton } from "@/components/back-button";

export default function BlogPost() {
    return (
        <article>
            <BackButton label="Back to blog" />
            {/* rest of content */}
        </article>
    );
}

Pattern B: Hardcoded Link

If you always want to go to a specific page, just use a regular Link:

import Link from "next/link";

export default function BlogPost() {
    return (
        <article>
            <Link href="/blog">Back to blog</Link>
            {/* rest of content */}
        </article>
    );
}

When to use which:

  • Use router.back() when users might arrive from multiple entry points
  • Use Link when the destination is always the same and you want consistent behavior

Styling the Back Button

A minimal, accessible back button with hover animation:

"use client";

import { useRouter } from "next/navigation";
import { ArrowLeft } from "lucide-react";

export const BackButton = ({ label }: { label: string }) => {
    const router = useRouter();

    return (
        <button
            onClick={() => router.back()}
            className="group flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
        >
            <ArrowLeft className="w-4 h-4 transition-transform group-hover:-translate-x-1" />
            {label}
        </button>
    );
};

Common Pitfalls

  1. Forgetting "use client" - Router hooks don't work in server components
  2. No fallback - Users opening pages directly will have nowhere to go back to
  3. Breaking SSG - Using router during render breaks static generation; only use in event handlers
  4. Hydration mismatches - Don't conditionally render based on window.history during initial render

Quick Reference

MethodUse Case
router.back()Dynamic back navigation with Next.js features
window.history.back()Simple back without router import
router.push(url)Fallback when no history exists
Link href=""Fixed destination navigation

That's the complete picture. For most cases, a simple router.back() in a client component does the job.

You might also like

See all