Handling Authorization and Caching in Next.js: A Developer’s Guide

Next.js introduces powerful experimental features for access control and smart caching. This guide covers the unauthorized() function for custom 401 handling, unstable_cache for persistent memoization, updateTag for instant cache invalidation, and useLinkStatus for inline navigation feedback. Learn how to use these tools to build secure, performant, and responsive applications.

unauthorizedunstable_cacheupdateTaguseLinkStatus

~2 min read • Updated Nov 1, 2025

1. Access Control with unauthorized()


unauthorized() throws a 401 error and renders the unauthorized.tsx page. It’s ideal for protecting sensitive routes and redirecting unauthenticated users to a login UI.


Enable in next.config.js:

experimental: {
  authInterrupts: true,
}

Example in Server Component:

const session = await verifySession()
if (!session) unauthorized()

Custom UI:

// app/unauthorized.tsx
<main>
  <h1>401 - Unauthorized</h1>
  <p>Please log in to access this page.</p>
  <Login />
</main>

2. Smart Caching with unstable_cache()


unstable_cache() memoizes expensive operations like database queries across requests and deployments.


Example:

const getCachedUser = unstable_cache(
  async (id) => fetchUser(id),
  ['user-cache-key'],
  { tags: ['users'], revalidate: 60 }
)

Tips:

  • Avoid accessing cookies or headers inside cached functions
  • Use tags for targeted invalidation
  • Use revalidate to set cache duration

3. Real-Time Invalidation with updateTag()


updateTag() expires cached data instantly after a mutation. It’s only available in Server Actions.


Example:

'use server'
import { updateTag } from 'next/cache'

export async function createPost(formData) {
  const post = await db.post.create({ data: formData })
  updateTag('posts')
  updateTag(`post-${post.id}`)
  redirect(`/posts/${post.id}`)
}

4. Opting Out of Caching with unstable_noStore()


unstable_noStore() disables caching and forces dynamic rendering.


Example:

import { unstable_noStore as noStore } from 'next/cache'

export default async function Component() {
  noStore()
  const data = await db.query(...)
}

5. Preserving Error Behavior with unstable_rethrow()


Use unstable_rethrow() to preserve Next.js error handling when catching errors like notFound() or redirect().


Example:

try {
  const res = await fetch(...)
  if (res.status === 404) notFound()
} catch (err) {
  unstable_rethrow(err)
}

6. Navigation Feedback with useLinkStatus()


useLinkStatus() tracks the pending state of a <Link> for subtle UI feedback during navigation.


Example:

'use client'
import { useLinkStatus } from 'next/link'

function Hint() {
  const { pending } = useLinkStatus()
  return <span className={`link-hint ${pending ? 'is-pending' : ''}`} />
}

Best Practices:

  • Use with prefetch={false}
  • Avoid layout shifts by animating opacity
  • Use route-level fallbacks with loading.js

Conclusion


With unauthorized() for access control, unstable_cache() for smart caching, updateTag() for real-time invalidation, and useLinkStatus() for smooth navigation, you can build secure, performant, and dynamic applications in Next.js.


Written & researched by Dr. Shahin Siami