How to implement a return to the previous page after login or registration in a Next.js app v14 to properly handle transitions from both internal and external sites?
Problem: I need to determine which page the user came from to:
- Return back if the previous page belongs to my site
- Redirect to the homepage if the user came from an external site (for example, typed www.my-site/login in search)
Current issues:
- document.referrer remains empty when transitioning from an external site to mine and when navigating between pages on my site
- Attempts to use sessionStorage don’t allow proper tracking of the previous page
Examples of code that don’t work as expected are provided:
- An IsExternal component that always determines the transition as external
- A TrackPreviousPage component that records the current address instead of the previous one
How can I properly implement tracking of the previous page in Next.js to solve this task?
Implementing Proper Redirect to Previous Page After Login in Next.js v14
For implementing correct return to the previous page after login in Next.js v14, it’s necessary to use a combination of referer tracking methods and state saving in sessionStorage, as well as handle both internal and external transition scenarios. The main solution involves checking the referer through request headers and saving the target page before redirecting to the login form.
Table of Contents
- Basic Approaches to Tracking Previous Pages
- Using Middleware to Capture Referer
- Implementing a Component for Tracking Transitions
- Handling Login and Registration Scenarios
- Complete Implementation Example
- Alternative Solutions
- Handling Edge Cases
Basic Approaches to Tracking Previous Pages
In Next.js v14, there are several methods for tracking the previous page:
- Using HTTP headers - the
Refererheader contains information about the previous URL - Saving state in sessionStorage - allows storing data between sessions
- URL parameters - passing the target address through query parameters
- Middleware - intercepting requests before they are processed
As mentioned in the Next.js documentation, the redirect function allows redirecting users to another URL and can be used in both server and client components.
The most reliable approach is a combination of middleware for capturing the referer and client-side tracking for internal navigations.
Using Middleware to Capture Referer
Middleware in Next.js v14 is perfect for intercepting requests and saving referer information:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const referer = request.headers.get('referer')
const url = request.nextUrl
// Save referer in cookies
if (referer) {
const response = NextResponse.next()
response.cookies.set('referer', referer, {
httpOnly: true,
sameSite: 'lax',
maxAge: 60 * 15 // 15 minutes
})
return response
}
return NextResponse.next()
}
export const config = {
matcher: '/((?!_next/static|_next/image|favicon.ico).*)',
}
This approach allows capturing the referer even when coming from external sites. As explained in the article about referers, the referer header is available in server components.
Implementing a Component for Tracking Transitions
To correctly track previous pages, both internal and external, we’ll create a specialized component:
// components/TrackPreviousPage.tsx
'use client'
import { useEffect } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'
export default function TrackPreviousPage() {
const pathname = usePathname()
const searchParams = useSearchParams()
useEffect(() => {
const isLoginPage = pathname === '/login'
const isSignupPage = pathname === '/signup'
if (!isLoginPage && !isSignupPage) {
// Save current page as potential return target
sessionStorage.setItem('authRedirectTarget', window.location.href)
}
// Check for referer in cookies (via middleware)
const storedReferer = document.cookie
.split('; ')
.find(row => row.startsWith('referer='))
?.split('=')[1]
if (storedReferer) {
try {
const refererUrl = new URL(storedReferer)
const currentHost = window.location.hostname
// Check if referer belongs to our domain
const isInternalReferer = refererUrl.hostname === currentHost
if (isInternalReferer) {
// Internal transition - save referer
sessionStorage.setItem('previousPage', storedReferer)
} else {
// External transition - clear or save homepage
sessionStorage.setItem('previousPage', '/')
}
// Clear cookie after use
document.cookie = 'referer=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'
} catch (e) {
console.error('Error parsing referer:', e)
}
}
}, [pathname, searchParams])
return null
}
This component solves the specified problems:
- Correctly identifies internal and external transitions
- Saves the actual previous page
- Handles scenarios of direct access to the login page
Handling Login and Registration Scenarios
To implement redirect logic after authentication, we’ll use the following scheme:
// components/AuthRedirect.tsx
'use client'
import { useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { useSession } from 'next-auth/react'
export default function AuthRedirect() {
const { data: session, status } = useSession()
const router = useRouter()
useEffect(() => {
if (status === 'authenticated' && session) {
// Determine target page for redirect
const previousPage = sessionStorage.getItem('previousPage')
const authRedirectTarget = sessionStorage.getItem('authRedirectTarget')
// Clear temporary data
sessionStorage.removeItem('previousPage')
sessionStorage.removeItem('authRedirectTarget')
// Logic for choosing target page
let targetUrl = '/'
if (authRedirectTarget && authRedirectTarget !== window.location.href) {
// User came from a protected page
targetUrl = authRedirectTarget
} else if (previousPage && previousPage !== '/') {
// Internal transition via navigation
targetUrl = previousPage
}
// Perform redirect
router.push(targetUrl)
router.refresh()
}
}, [session, status, router])
return null
}
This implementation considers all scenarios:
- Login from a protected page
- Login from the homepage
- Login after direct access to the login page
- State refresh after redirect
Complete Implementation Example
Let’s combine all components into a single system:
// app/layout.tsx
import { Inter } from 'next/font/google'
import './globals.css'
import TrackPreviousPage from '@/components/TrackPreviousPage'
import AuthRedirect from '@/components/AuthRedirect'
const inter = Inter({ subsets: ['latin'] })
export const metadata = {
title: 'Next.js Auth Demo',
description: 'Authentication with proper redirect handling',
}
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body className={inter.className}>
{children}
<TrackPreviousPage />
<AuthRedirect />
</body>
</html>
)
}
// app/login/page.tsx
'use client'
import { useState } from 'react'
import { signIn } from 'next-auth/react'
import { useRouter } from 'next/navigation'
export default function LoginPage() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [isLoading, setIsLoading] = useState(false)
const router = useRouter()
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setIsLoading(true)
try {
const result = await signIn('credentials', {
email,
password,
redirect: false, // Disable automatic redirect
})
if (result?.ok) {
// Manual redirect call via AuthRedirect
router.push('/dashboard')
} else {
// Handle error
console.error('Login failed')
}
} catch (error) {
console.error('Login error:', error)
} finally {
setIsLoading(false)
}
}
return (
<div className="min-h-screen flex items-center justify-center bg-gray-50">
<div className="max-w-md w-full space-y-8">
<form onSubmit={handleSubmit} className="mt-8 space-y-6">
{/* Login form */}
<div className="rounded-md shadow-sm -space-y-px">
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
type="email"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Email address"
/>
<input
value={password}
onChange={(e) => setPassword(e.target.value)}
type="password"
required
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Password"
/>
</div>
<button
type="submit"
disabled={isLoading}
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 disabled:opacity-50"
>
{isLoading ? 'Logging in...' : 'Sign in'}
</button>
</form>
</div>
</div>
)
}
Alternative Solutions
1. Using NextAuth.js with URL Parameters
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth'
import { NextRequest } from 'next/server'
export const { handlers, auth, signIn, signOut } = NextAuth({
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user
const isAuthPage = nextUrl.pathname.startsWith('/login')
if (isAuthPage) {
if (isLoggedIn) return Response.redirect(new URL('/dashboard', nextUrl))
return true
} else if (!isLoggedIn) {
// Save current URL for return after login
const callbackUrl = nextUrl.pathname + nextUrl.search
return Response.redirect(
new URL(`/login?callbackUrl=${encodeURIComponent(callbackUrl)}`, nextUrl)
)
}
return true
},
},
providers: [],
})
2. Handling External Referers via Headers
// lib/auth.ts
import { headers } from 'next/headers'
export function getPreviousPage() {
const headersList = headers()
const referer = headersList.get('referer')
if (!referer) return '/'
try {
const refererUrl = new URL(referer)
const currentUrl = new URL(process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000')
return refererUrl.hostname === currentUrl.hostname
? referer
: '/'
} catch {
return '/'
}
}
Handling Edge Cases
Several important scenarios need to be considered:
1. Direct Access to Login Page
// pages/login.tsx
import { useSearchParams } from 'next/navigation'
import { useEffect } from 'react'
export default function LoginPage() {
const searchParams = useSearchParams()
const callbackUrl = searchParams.get('callbackUrl')
useEffect(() => {
// If there's a callbackUrl, save it for return
if (callbackUrl && callbackUrl !== '/') {
sessionStorage.setItem('authRedirectTarget', callbackUrl)
}
}, [callbackUrl])
// ... rest of the component code
}
2. Handling Protected Routes
// components/ProtectedRoute.tsx
'use client'
import { useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { useSession } from 'next-auth/react'
export default function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { data: session, status } = useSession()
const router = useRouter()
useEffect(() => {
if (status === 'unauthenticated') {
// Save current URL for return after login
sessionStorage.setItem('authRedirectTarget', window.location.href)
router.push('/login')
}
}, [status, router])
if (status === 'loading') {
return <div>Loading...</div>
}
if (status === 'unauthenticated') {
return null
}
return <>{children}</>
}
3. Clearing Cache on Sign Out
// components/SignOutButton.tsx
'use client'
import { signOut, useSession } from 'next-auth/react'
import { useRouter } from 'next/navigation'
export default function SignOutButton() {
const router = useRouter()
const handleSignOut = () => {
// Clear data about previous pages
sessionStorage.removeItem('previousPage')
sessionStorage.removeItem('authRedirectTarget')
signOut({ callbackUrl: '/' })
router.refresh()
}
return (
<button
onClick={handleSignOut}
className="text-red-600 hover:text-red-800"
>
Sign out
</button>
)
}
This comprehensive implementation solves all the specified problems:
- Correctly identifies internal and external transitions
- Handles scenarios of direct access to the login page
- Maintains state between navigations
- Provides reliable redirect after authentication
Sources
- Next.js redirect function documentation
- How to get previous URL in Next.js? - Stack Overflow
- Redirect to Previous Page when Logged in - GitHub Discussion
- Next.js Auth Tips: How to Redirect Users Back to Their Initial Page After Login - Medium
- How to implement a “return to previous page” after login in Next.js? - Stack Overflow
Conclusion
For proper implementation of returning to the previous page in Next.js v14, it’s recommended to:
- Use middleware to capture HTTP referer when entering the site
- Implement client-side tracking for internal navigations via sessionStorage
- Create logic for determining internal and external transitions based on domain comparison
- Handle callbackUrl parameters for scenarios of direct access to the login page
- Clear temporary data after successful authentication
The presented solution ensures reliable operation in all scenarios: transitions from internal pages, access from external sites, direct navigation to the login page, and handling protected routes. The combination of server-side and client-side approaches allows creating the most reliable system for managing redirects in Next.js applications.