Softotic
Back to insights
18 August 202617 min readNext.jsReactWeb DevelopmentPerformanceTroubleshooting

Next.js 16 'Uncached Data Outside Suspense' Error: How to Fix It Properly

A diagnosis-first guide to the Next.js 16 'Uncached data was accessed outside of <Suspense>' error, covering Cache Components, use cache, Suspense, connection(), request data, Route Handlers, and self-hosted deployments.

Next.js 16 'Uncached Data Outside Suspense' Error: How to Fix It Properly

If a Next.js 16 application fails with “Uncached data was accessed outside of __INLINE_CODE_0__”, the correct fix is not automatically to wrap the whole page in __INLINE_CODE_1__.

The error appears when Cache Components is enabled and Next.js encounters asynchronous or request-time work that cannot be completed during prerendering. Next.js needs you to make the intended behaviour explicit:

  • cache the result if the same output can be reused
  • stream it behind __INLINE_CODE_0__ if it should run for each request
  • use request-time APIs such as __INLINE_CODE_0__ when the work must deliberately wait for an incoming request

The important question is therefore not “How do I silence this error?”

It is:

Should this data be shared, request-specific, or deliberately dynamic?

That decision determines whether your fix improves performance or accidentally serves stale or user-specific data in the wrong way.

Why does Next.js 16 throw this error?

Next.js 16 introduced the __INLINE_CODE_0__ configuration as the unified model behind Cache Components and Partial Prerendering.

When enabled:

ts
// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  cacheComponents: true,
}

export default nextConfig

Next.js attempts to prerender the parts of a route that can be produced before a request arrives.

According to the current Next.js Cache Components documentation, components that perform uncached asynchronous work cannot simply block that prerender indefinitely. They must either become cacheable with __INLINE_CODE_0__, or be deferred to request time behind a React __INLINE_CODE_1__ boundary.

That is why code such as this can trigger the error:

tsx
async function RecentOrders() {
  const orders = await db.order.findMany({
    orderBy: { createdAt: 'desc' },
  })

  return <OrderList orders={orders} />
}

export default async function Page() {
  return (
    <main>
      <h1>Orders</h1>
      <RecentOrders />
    </main>
  )
}

The database call is asynchronous and uncached. Next.js cannot confidently include it in the prerendered shell, but there is also no __INLINE_CODE_0__ boundary telling Next.js what to show while that request-time work completes.

The error is therefore a rendering-intent error, not merely a database error.

The three-way decision that fixes most cases

Before changing code, classify the data.

QuestionBest starting direction
Can many users share the same result for some period?__INLINE_CODE_0__
Must the result be fresh on each request?__INLINE_CODE_0__
Does the code intentionally depend on incoming request/runtime state?Request-time rendering / __INLINE_CODE_0__ where appropriate
Does the result depend on a user's cookies, headers or session?Keep personalised work request-specific
Is it a short-lived cached result?Check whether its __INLINE_CODE_0__ still makes it a dynamic hole
Is this a GET Route Handler reading the Request synchronously?__INLINE_CODE_0__ before request access

These two fixes can both satisfy the build while producing different behaviour:

tsx
// Shared result
async function Products() {
  'use cache'
  return db.product.findMany()
}

versus:

tsx
// Fresh/request-time result
async function Products() {
  return db.product.findMany()
}

export default function Page() {
  return (
    <Suspense fallback={<ProductsSkeleton />}>
      <Products />
    </Suspense>
  )
}

The correct choice depends on freshness, server load and personalisation.

Fix 1: use __INLINE_CODE_0__ when the result can be shared

Use __INLINE_CODE_0__ when the output is safe to reuse across requests for an appropriate period.

A typical example is a public product catalogue:

tsx
import { cacheLife, cacheTag } from 'next/cache'

async function getProducts() {
  'use cache'

  cacheLife('hours')
  cacheTag('products')

  return db.product.findMany({
    where: { published: true },
    orderBy: { name: 'asc' },
  })
}

export default async function ProductsPage() {
  const products = await getProducts()

  return <ProductGrid products={products} />
}

The current __INLINE_CODE_0__ documentation allows caching at the file, component or function level.

Using it close to the data operation is often easier to reason about than caching an entire page.

Why this fixes the error

By marking the function cacheable, you are telling Next.js:

This result does not need to be recomputed uniquely for every incoming request.

Next.js can therefore use the result as part of the cached/prerendered rendering model.

When this is a good fit

Good candidates include:

  • public blog content
  • product catalogue data
  • public category/navigation data
  • configuration shared by users
  • CMS content
  • reference data
  • data that can tolerate controlled staleness

When this is a bad fit

Do not blindly cache:

  • current user's account data
  • shopping-cart contents tied to a session
  • private dashboard data
  • permission-sensitive output
  • request-specific geolocation
  • data that must reflect every write immediately unless invalidation is designed correctly

Caching is a behavioural decision, not a compiler annotation.

Add an explicit cache lifetime

A cached result needs a freshness strategy.

Next.js provides __INLINE_CODE_0__ for predefined or custom cache profiles.

tsx
import { cacheLife } from 'next/cache'

async function getArticles() {
  'use cache'
  cacheLife('hours')

  return db.article.findMany({
    where: { status: 'published' },
  })
}

Current Next.js revalidation documentation provides built-in profiles such as __INLINE_CODE_0__, __INLINE_CODE_1__, __INLINE_CODE_2__, __INLINE_CODE_3__, __INLINE_CODE_4__ and __INLINE_CODE_5__.

Choose the profile from the business meaning of stale data:

text
Marketing page
→ hours/days may be fine

Product availability
→ shorter or event-invalidated

Account balance
→ should not become a broadly shared cache entry

Use cache tags when writes should invalidate specific data

Suppose product data can remain cached for hours, but an admin publishing a product should make it visible promptly.

Tag the cached operation:

tsx
import { cacheLife, cacheTag } from 'next/cache'

async function getProducts() {
  'use cache'

  cacheLife('hours')
  cacheTag('products')

  return db.product.findMany()
}

Then invalidate that tag after a mutation.

For a Server Action where the user should immediately see their own change:

tsx
'use server'

import { updateTag } from 'next/cache'

export async function createProduct(formData: FormData) {
  await db.product.create({
    data: {
      name: String(formData.get('name')),
    },
  })

  updateTag('products')
}

The current __INLINE_CODE_0__ documentation says it is Server-Action-only and designed for read-your-own-writes behaviour.

For other contexts such as Route Handlers or webhook endpoints, use __INLINE_CODE_0__ instead.

Caching should be paired with an invalidation plan.

Fix 2: use __INLINE_CODE_0__ when the data should run per request

If data genuinely needs to be accessed for every request, do not cache it simply to satisfy the build.

Instead, isolate the dynamic part:

tsx
import { Suspense } from 'react'

async function LiveOrders() {
  const orders = await db.order.findMany({
    orderBy: { createdAt: 'desc' },
  })

  return <OrderList orders={orders} />
}

export default function Page() {
  return (
    <main>
      <h1>Live Orders</h1>

      <Suspense fallback={<OrdersSkeleton />}>
        <LiveOrders />
      </Suspense>
    </main>
  )
}

Next.js can prerender the surrounding shell while deferring the live data to request time.

This is one of the central ideas behind Cache Components: a route does not have to be entirely static or entirely dynamic.

Put __INLINE_CODE_0__ around the smallest useful dynamic subtree

A tempting fix is:

tsx
export default function Page() {
  return (
    <Suspense fallback={<FullPageSpinner />}>
      <EntirePage />
    </Suspense>
  )
}

That can remove the error while throwing away much of the benefit of partial prerendering.

Prefer:

tsx
export default function Page() {
  return (
    <>
      <StaticHeader />
      <StaticSummary />

      <Suspense fallback={<OrdersSkeleton />}>
        <LiveOrders />
      </Suspense>

      <StaticFooter />
    </>
  )
}

The principle is:

Make the dynamic hole as small as the product experience allows.

What if the data depends on __INLINE_CODE_0__ or __INLINE_CODE_1__?

Request APIs need special care.

tsx
import { cookies } from 'next/headers'

async function AccountPanel() {
  const cookieStore = await cookies()
  const session = cookieStore.get('session')

  return getAccountPanel(session?.value)
}

Request data is tied to an incoming request.

The current Next.js error documentation notes that __INLINE_CODE_0__, __INLINE_CODE_1__ and __INLINE_CODE_2__ are asynchronous APIs, and Cache Components makes synchronous legacy access an error.

Avoid:

tsx
const token = cookies().get('token')

Use:

tsx
const cookieStore = await cookies()
const token = cookieStore.get('token')

and keep personalised work in an appropriate request-time/Suspense boundary.

Do not read cookies inside a normal shared __INLINE_CODE_0__ scope

Next.js recommends reading request data outside cached scopes and passing values as arguments when that cache design is appropriate.

But do not turn user-specific data into a shared cache simply to make the build pass. You still need to decide whether personal data should be cached, for how long, and under what deployment and privacy constraints.

__INLINE_CODE_0__ and __INLINE_CODE_1__ are asynchronous too

Another migration trap is treating page parameters synchronously.

Avoid:

tsx
export default function Page({ searchParams }) {
  const query = searchParams.q

  return <Search query={query} />
}

Prefer:

tsx
export default async function Page({ searchParams }) {
  const { q } = await searchParams

  return <Search query={q} />
}

Or pass the promise into a smaller component behind __INLINE_CODE_0__ when that better matches the route.

The current Next.js synchronous params error documentation explains that Cache Components no longer tolerates the legacy synchronous compatibility behaviour.

If a codemod could not convert an occurrence, search the project for:

text
@next-codemod-error

What if a GET Route Handler reads the Request object?

A related error occurs in __INLINE_CODE_0__ when Cache Components is enabled and a GET handler accesses Request information synchronously.

If the route should explicitly wait for an incoming request, use __INLINE_CODE_0__:

ts
import { connection } from 'next/server'

export async function GET(request: Request) {
  await connection()

  const url = new URL(request.url)

  return Response.json({
    pathname: url.pathname,
  })
}

The current __INLINE_CODE_0__ API tells Next.js that execution below that point should wait for an incoming request instead of being prerendered.

The dedicated Route Handler error documentation notes that this pattern is needed for GET because GET is the method Next.js may attempt to prerender.

When should you use __INLINE_CODE_0__ in a page or component?

__INLINE_CODE_0__ is useful when work must intentionally be generated at request time but the code is not already using an obvious dynamic request API.

tsx
import { connection } from 'next/server'

export default async function Page() {
  await connection()

  const generatedAt = new Date().toISOString()

  return <p>Generated at {generatedAt}</p>
}

A useful mental model is:

text
'use cache'
→ this work can be reused

<Suspense>
→ this subtree may wait/stream

await connection()
→ do not continue this work until a request exists

These mechanisms solve related but different problems.

Why a very short __INLINE_CODE_0__ can still behave dynamically

A subtle problem appears when a function has __INLINE_CODE_0__ but a very short cache lifetime.

Next.js documentation says short-lived entries can be excluded from prerendering and become dynamic holes.

For example:

tsx
import { cacheLife } from 'next/cache'

async function getDashboard() {
  'use cache'
  cacheLife('seconds')

  return db.dashboard.findMany()
}

If Next.js cannot include the work in the prerender, you may still need a __INLINE_CODE_0__ boundary.

So the debugging question is not:

“Does the function contain __INLINE_CODE_0__?”

It is:

Can Next.js actually use this result during the prerendering model I configured?

The most common wrong fixes

Wrong fix 1: cache the entire authenticated page

A build problem can become a data-freshness or personalisation problem.

Cache only what is actually shareable.

Wrong fix 2: put one giant __INLINE_CODE_0__ around everything

The build passes, but users receive a spinner where Next.js could have served a useful shell.

Use smaller boundaries.

Wrong fix 3: disable __INLINE_CODE_0__ without understanding why

Turning Cache Components off can intentionally keep a project on the previous caching model, but it should be a migration decision rather than a permanent mystery switch.

Next.js still documents the previous caching model for applications not using Cache Components.

Wrong fix 4: add __INLINE_CODE_0__ to permission-sensitive data

A compiler error disappearing does not prove the caching boundary is safe.

Review privacy, permissions and invalidation.

Wrong fix 5: use __INLINE_CODE_0__ everywhere

__INLINE_CODE_0__ moves work to request time.

If the data is public and reusable, making everything dynamic can add latency and server load unnecessarily.

Wrong fix 6: assume only __INLINE_CODE_0__ matters

Uncached asynchronous work can include database clients, ORMs, SDK calls, filesystem/network operations and custom async modules.

A diagnosis workflow that avoids guesswork

Step 1: find the exact async boundary

Use the build/dev error to identify the component or function performing uncached work.

Do not immediately edit the root layout.

Step 2: identify the data owner

Ask whether the data is:

  • public
  • user-specific
  • request-specific
  • admin-only
  • frequently changing
  • derived from cookies/headers
  • from an external API
  • from a database

Step 3: choose freshness

Ask:

text
Can this result be stale for 1 minute?
1 hour?
1 day?
Never?

Step 4: choose the mechanism

text
Reusable
→ 'use cache' + cacheLife

Reusable but mutation-aware
→ 'use cache' + cacheTag + invalidation

Fresh per request
→ Suspense

Explicitly request-time/non-deterministic
→ connection() + appropriate Suspense boundary

Request data
→ await cookies/headers/params/searchParams
  and keep the rendering boundary explicit

Step 5: test behaviour, not just the build

Verify:

  • first request
  • second request
  • stale window
  • mutation followed by refresh
  • another user/session
  • logged-out state
  • direct URL load
  • client-side navigation

A cache bug often hides because the first request looks correct.

Example: public product catalogue

Requirement:

  • public
  • can be a few minutes stale
  • admin updates should propagate quickly
tsx
import { cacheLife, cacheTag } from 'next/cache'

async function getProducts() {
  'use cache'

  cacheLife('minutes')
  cacheTag('products')

  return db.product.findMany({
    where: { published: true },
  })
}

export default async function ProductsPage() {
  const products = await getProducts()

  return <ProductGrid products={products} />
}

Then use tag invalidation after a product mutation.

Example: personalised account dashboard

Requirement:

  • user-specific
  • current session matters
  • data should be fresh
tsx
import { Suspense } from 'react'
import { cookies } from 'next/headers'

async function AccountDashboard() {
  const cookieStore = await cookies()
  const session = cookieStore.get('session')

  const account = await getAccountForSession(session?.value)

  return <Dashboard account={account} />
}

export default function Page() {
  return (
    <>
      <AccountHeader />

      <Suspense fallback={<DashboardSkeleton />}>
        <AccountDashboard />
      </Suspense>
    </>
  )
}

The shell can be prepared separately from the personalised content.

Example: search page using __INLINE_CODE_0__

A search page is naturally request/query-specific.

tsx
import { Suspense } from 'react'

async function SearchResults({
  searchParams,
}: {
  searchParams: Promise<{ q?: string }>
}) {
  const { q = '' } = await searchParams

  const results = await searchProducts(q)

  return <Results items={results} />
}

export default function Page({
  searchParams,
}: {
  searchParams: Promise<{ q?: string }>
}) {
  return (
    <>
      <h1>Search</h1>

      <Suspense fallback={<SearchSkeleton />}>
        <SearchResults searchParams={searchParams} />
      </Suspense>
    </>
  )
}

This keeps the static portion available without pretending each search result is reusable.

What changes when Next.js is self-hosted?

The rendering logic is only half of the production story.

Current Next.js self-hosting documentation explains that cache behaviour across multiple processes or containers needs deliberate coordination.

For Cache Components, normal __INLINE_CODE_0__ entries use in-memory storage by default unless the platform or application configures another cache handler.

That can produce:

text
Development laptop
→ one process
→ caching looks consistent

Multi-instance production
→ several processes
→ each process may hold different cache state

Next.js provides __INLINE_CODE_0__ and __INLINE_CODE_1__ for scenarios where shared/durable cache storage is justified.

Do not add Redis automatically

Shared cache infrastructure adds network latency, operational complexity, eviction behaviour and failure modes.

Next.js notes that most applications do not need custom cache handlers.

Use remote/shared caching when the production topology requires it, not because every architecture diagram deserves a Redis cylinder.

A production-ready checklist

Before closing the migration issue, verify:

  • __INLINE_CODE_0__ is enabled intentionally
  • every reported uncached operation has been classified
  • public reusable data has an explicit caching policy
  • cache lifetime matches business freshness requirements
  • mutations have appropriate invalidation
  • request-specific data is not accidentally shared
  • __INLINE_CODE_0__ and __INLINE_CODE_1__ are awaited
  • __INLINE_CODE_0__ and __INLINE_CODE_1__ use current async patterns
  • dynamic subtrees have useful __INLINE_CODE_0__ fallbacks
  • __INLINE_CODE_0__ boundaries are not unnecessarily broad
  • __INLINE_CODE_0__ is used only where deliberate request-time execution is needed
  • GET Route Handlers that read request state follow current patterns
  • authenticated flows are tested with multiple sessions
  • self-hosted multi-instance cache behaviour has been reviewed
  • the fix was tested for freshness after writes, not only build success

If those are true, you have fixed the rendering model, not merely the error message.

FAQs

Why does Next.js 16 say “Uncached data was accessed outside of __INLINE_CODE_0__”?

With Cache Components enabled, Next.js prerenders the parts of a route it can complete ahead of a request. Uncached asynchronous work that needs request-time execution must be placed behind __INLINE_CODE_0__, while reusable work can be explicitly cached with __INLINE_CODE_1__.

Should I use __INLINE_CODE_0__ or __INLINE_CODE_1__?

Use __INLINE_CODE_0__ when the result can safely be reused for an appropriate period. Use __INLINE_CODE_1__ when the data should be fetched or computed for each request. The correct choice depends on freshness and personalisation.

Can I use __INLINE_CODE_0__ with database queries?

Yes. Cache Components are not limited to __INLINE_CODE_0__. A cached async function can wrap database or other asynchronous operations when the result is appropriate to cache.

Why do I still get the error after adding __INLINE_CODE_0__?

Check the cache lifetime and request dependencies. Very short-lived caches can still behave as dynamic holes, and request APIs such as cookies or headers cannot simply be read inside a normal shared cache scope.

Do __INLINE_CODE_0__ and __INLINE_CODE_1__ need __INLINE_CODE_2__ in Next.js 16?

Yes. These request APIs are asynchronous in the modern App Router model. Cache Components surfaces incorrect synchronous access as an error rather than relying on legacy compatibility behaviour.

What does __INLINE_CODE_0__ do?

__INLINE_CODE_0__ tells Next.js to wait for an incoming request before continuing. It is useful when work must be dynamic at request time even though the code does not otherwise use a dynamic API that naturally signals that requirement.

Conclusion

The Next.js 16 “Uncached data was accessed outside of __INLINE_CODE_0__” error is useful once you stop treating it as a syntax problem.

It forces the application to answer an architectural question:

text
Can this result be reused?
        ↓ yes
'use cache'

Must it happen per request?
        ↓ yes
<Suspense>

Must execution deliberately wait for request-time state?
        ↓ yes
connection() / request APIs

The best fix is the one that preserves the correct freshness, personalisation and performance semantics.

If a dashboard must be fresh, do not cache it just to satisfy the build.

If public catalogue data changes once an hour, do not recompute it for every visitor merely because __INLINE_CODE_0__ is easy.

And if the application runs across several self-hosted instances, make sure production cache behaviour matches the assumptions made locally.

Need help migrating or debugging a production Next.js application? Softotic's web application development service covers Next.js architecture, performance and deployment, while custom software development can support the backend and data layer behind it.

Sources and references