nextjs - 💡(How to fix) Fix [Cache Components] Direct DB Access (e.g., SQLite) behaves as Static after Build [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#86376Fetched 2026-04-08 02:11:20
View on GitHub
Comments
1
Participants
2
Timeline
5
Reactions
0
Timeline (top)
closed ×1commented ×1issue_type_added ×1labeled ×1

Error Message

"use server" // Using a generic database connection object const db = getDb();

export async function getRotationalData() {

// 1. Query to retrieve the current item based on DB logic/mutation const currentItem = db.prepare('SELECT * FROM items WHERE status = "active" ORDER BY last_use_time').get();

if (!currentItem) return { Error: "No items available" };

// 2. CRUCIAL STEP: Data mutation (DB write operation) // This logic should execute on every request, but the result is cached. db.prepare( UPDATE items SET usage_count = usage_count + 1, last_use_time = datetime('now') WHERE id = ? ).run(currentItem.id);

return currentItem; }

Root Cause

Current Behavior After running next build and next start, the output of DynamicDataComponent does not change across multiple requests. The DB mutation (UPDATE) inside getRotationalData is executed only once, proving that the result of the async function is cached statically. This happens because Next.js appears to cache the result of asynchronous functions that do not use the native fetch API, even if the surrounding component is dynamic.

Code Example

import { Suspense } from "react";
import DynamicDataComponent from "./DynamicDataComponent";

// NO export const dynamic = 'force-dynamic'; is used here, 
// relying on the default dynamic behavior of Server Components.

export default async function HoyPage() {
  return (
    <>
      <div>Welcome to the dynamic page</div>
      <Suspense fallback={<p>Loading data...</p>}>
        <DynamicDataComponent />
      </Suspense>
    </>
  );
}

---

import { getRotationalData } from "@/libs/data";

export default async function DynamicDataComponent() {
  // This call is expected to run on every request.
  const data = await getRotationalData(); 

  return (
    <div className="data-display">
      <h2>Current Item ID: {data.id}</h2>
      <p>Last Used Timestamp: {data.last_use_time}</p> 
    </div>
  );
}

---

"use server"
// Using a generic database connection object
const db = getDb(); 

export async function getRotationalData() {
  
  // 1. Query to retrieve the current item based on DB logic/mutation
  const currentItem = db.prepare('SELECT * FROM items WHERE status = "active" ORDER BY last_use_time').get();

  if (!currentItem) return { Error: "No items available" };

  // 2. CRUCIAL STEP: Data mutation (DB write operation) 
  //    This logic should execute on every request, but the result is cached.
  db.prepare(`
    UPDATE items 
    SET usage_count = usage_count + 1,
    last_use_time = datetime('now')
    WHERE id = ?
  `).run(currentItem.id);

  return currentItem;
}

---

Operating System:
  Platform: win32
  Arch: x64
  Version: Windows 10 Pro
  Available memory (MB): 16327
  Available CPU cores: 12
Binaries:
  Node: 22.13.1
  npm: 10.9.0
  Yarn: N/A
  pnpm: N/A
Relevant Packages:
  next: 16.0.3 // Latest available version is detected (16.0.3).
  eslint-config-next: N/A
  react: 19.2.0
  react-dom: 19.2.0
  typescript: 5.9.3
Next.js Config:
  output: N/A
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://no-link.no

To Reproduce

Create a Server Component (DynamicDataComponent.js) which calls an asynchronous server function (getRotationalData).

The function getRotationalData directly accesses and mutates an external resource (specifically, a local database like SQLite) using a database client (e.g., db.query(), db.run()) instead of the native fetch API. The function logic is designed to return dynamic data that changes with every execution (e.g., increments a counter or rotates an item's status, and crucially, performs an UPDATE in the DB).

Place this Server Component inside a root page (e.g., app/page.js).

Ensure the page DOES NOT use export const dynamic = 'force-dynamic', adhering to the current App Router documentation.

Run the production build command: npm run build.

Start the application: npm run start.

Access the page repeatedly in the browser.

Relevant Code Example This is the structure of the relevant files:

app/page.js (Page Component):

import { Suspense } from "react";
import DynamicDataComponent from "./DynamicDataComponent";

// NO export const dynamic = 'force-dynamic'; is used here, 
// relying on the default dynamic behavior of Server Components.

export default async function HoyPage() {
  return (
    <>
      <div>Welcome to the dynamic page</div>
      <Suspense fallback={<p>Loading data...</p>}>
        <DynamicDataComponent />
      </Suspense>
    </>
  );
}

app/DynamicDataComponent.js (Server Component):

import { getRotationalData } from "@/libs/data";

export default async function DynamicDataComponent() {
  // This call is expected to run on every request.
  const data = await getRotationalData(); 

  return (
    <div className="data-display">
      <h2>Current Item ID: {data.id}</h2>
      <p>Last Used Timestamp: {data.last_use_time}</p> 
    </div>
  );
}

libs/data.js (Server Action / DB Access):

"use server"
// Using a generic database connection object
const db = getDb(); 

export async function getRotationalData() {
  
  // 1. Query to retrieve the current item based on DB logic/mutation
  const currentItem = db.prepare('SELECT * FROM items WHERE status = "active" ORDER BY last_use_time').get();

  if (!currentItem) return { Error: "No items available" };

  // 2. CRUCIAL STEP: Data mutation (DB write operation) 
  //    This logic should execute on every request, but the result is cached.
  db.prepare(`
    UPDATE items 
    SET usage_count = usage_count + 1,
    last_use_time = datetime('now')
    WHERE id = ?
  `).run(currentItem.id);

  return currentItem;
}

Current vs. Expected behavior

Current Behavior After running next build and next start, the output of DynamicDataComponent does not change across multiple requests. The DB mutation (UPDATE) inside getRotationalData is executed only once, proving that the result of the async function is cached statically. This happens because Next.js appears to cache the result of asynchronous functions that do not use the native fetch API, even if the surrounding component is dynamic.

Expected Behavior When a Server Component calls an asynchronous function that accesses a database directly (not via fetch) and performs a mutation, Next.js should infer that the resulting data is dynamic and non-cacheable. The expectation is that the function getRotationalData should re-execute on every server request to ensure the latest data (and mutation) logic is applied.

Provide environment information

Operating System:
  Platform: win32
  Arch: x64
  Version: Windows 10 Pro
  Available memory (MB): 16327
  Available CPU cores: 12
Binaries:
  Node: 22.13.1
  npm: 10.9.0
  Yarn: N/A
  pnpm: N/A
Relevant Packages:
  next: 16.0.3 // Latest available version is detected (16.0.3).
  eslint-config-next: N/A
  react: 19.2.0
  react-dom: 19.2.0
  typescript: 5.9.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

cacheComponents

Which stage(s) are affected? (Select all that apply)

next build (local)

Additional context

The absence of export const dynamic = 'force-dynamic' in app/page.js is intentional. We are relying on the documented default behavior of Server Components in the App Router, which states that pages are dynamic by default. Previously, we would have used this configuration, but removing it highlighted the core issue: the default "dynamic" setting does not prevent caching of non-fetch data access results during next build, contrary to the expectation for an inherently dynamic, mutating data operation.

extent analysis

TL;DR

The most likely fix is to use export const dynamic = 'force-dynamic' in the page component to prevent caching of the asynchronous function result.

Guidance

  • The issue is likely caused by Next.js caching the result of the asynchronous function getRotationalData because it does not use the native fetch API.
  • To verify this, check the Next.js documentation on caching and Server Components to see if there are any specific guidelines for handling database mutations.
  • Consider using export const dynamic = 'force-dynamic' in the page component to force Next.js to re-execute the asynchronous function on every request.
  • Alternatively, investigate using a caching library or mechanism that can handle database mutations and dynamic data correctly.

Example

No code example is provided as the issue is more related to configuration and caching behavior.

Notes

The issue may be specific to the version of Next.js being used (16.0.3), and upgrading to a newer version may resolve the issue. However, without further information, it is difficult to determine the best course of action.

Recommendation

Apply workaround: use export const dynamic = 'force-dynamic' in the page component to prevent caching of the asynchronous function result. This is because the default dynamic behavior of Server Components does not seem to handle database mutations correctly, and forcing dynamic behavior may resolve the issue.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING