5. Data Fetching, Cache & Revalidation - Detailed Guide
This document explains how Next.js fetches and caches data in the App Router, and how Nova Shop applies it (revalidateTag, revalidatePath, refresh()).
On this page
- Table of contents
- 1. The problem to solve
- 2. Where does data fetching run in Nova Shop?
- 3. Four cache layers - understand before you code
- 4. fetch() options - cache, revalidate, tags
- 5. revalidateTag - clear cache by label
- 6. revalidatePath - force a route to re-render
- 7. refresh() - refresh the current route
- 8. Why does Nova use all three?
- 9. Real-world flows in Nova Shop
- 10. authFetch & the service layer
- 11. FAQ & common mistakes
- 12. File map in the repo
- 13. Route Segment Config - role & relationship to cache
- Appendix: fetch options quick reference
- Self-check exercises
5. Data Fetching, Cache & Revalidation - Detailed Guide
This document explains how Next.js fetches and caches data in the App Router, and how Nova Shop applies it (revalidateTag, revalidatePath, refresh()).
Reading order: Section 1 → 2 → 3 (foundations) → 4–6 (the 3 revalidation tools) → 7 (Nova Shop end-to-end) → 8 (FAQ).
Official docs: Fetching · Caching · Mutating Data
Table of contents
- The problem to solve
- Where does data fetching run in Nova Shop?
- Four cache layers - understand before you code
fetch()options - cache, revalidate, tagsrevalidateTag- clear cache by labelrevalidatePath- force a route to re-renderrefresh()- refresh the current route- Why does Nova use all three?
- Real-world flows in Nova Shop
- authFetch & the service layer
- FAQ & common mistakes
- File map in the repo
- Route Segment Config - role & relationship to cache
1. The problem to solve
When a user opens /products, Next.js has to:
- Call the NestJS API to fetch the product list.
- Render the HTML/React Server Components (RSC).
- Send the result down to the browser.
If every click calls the API from scratch → slow, wastes server resources.
If the cache is too aggressive → the user sees a stale cart after adding a product.
→ We need a caching strategy + a way to "tell" Next.js when data has changed (revalidate).
2. Where does data fetching run in Nova Shop?
Nova Shop does not call the API directly in a page's JSX. The standard flow:
┌─────────────────────────────────────────────────────────────┐
│ Browser │
│ Only receives HTML + RSC payload (data already embedded) │
└───────────────────────────▲─────────────────────────────────┘
│
┌───────────────────────────┴─────────────────────────────────┐
│ Next.js Server │
│ │
│ page.tsx (Server Component, async) │
│ │ │
│ ▼ │
│ getProducts() / getCartSummary() ← app/lib/services/*.ts │
│ │ │
│ ▼ │
│ authFetch() / fetch() ← app/lib/api-client.ts │
│ │ │
│ ▼ │
│ NestJS API (NEXT_PUBLIC_EXTERNAL_API_URL) │
└─────────────────────────────────────────────────────────────┘
Real example - app/(shop)/products/page.tsx:
export default async function ProductsPage(props) {
const filters = parseProductFilters(await props.searchParams);
const result = await getProducts({ ...filters }); // ← fetch on the SERVER
return <ListProductsComponent products={result.products} />;
}
| Question | Answer |
|---|---|
Does getProducts run in the browser? | No - only on the server (Server Component or Server Action). |
| Does the client see the NestJS API URL? | It may see the NEXT_PUBLIC_* env var; the token is not exposed (HttpOnly cookie + server fetch). |
| After the user adds to cart, who updates the cache? | The addToCart Server Action → revalidateAfterCartChange(). |
3. Four cache layers - understand before you code
Next.js doesn't have just "one cache." There are 4 layers (per the Caching Guide):
3.1 Request memoization (within one request)
Within the same server render pass (one HTTP request):
// Both lines share the same fetch result - the API isn't called twice
const a = await getProducts({ page: 1 });
const b = await getProducts({ page: 1 });
→ Saves work when the layout and page both need the same data.
3.2 Data Cache (most important for fetch)
When you fetch(url, { next: { revalidate: 60 } }), Next.js stores the response in the Data Cache.
- The next request within 60 seconds → may use the cached copy, no API call.
- After 60 seconds → background revalidation (ISR) or a fresh fetch, depending on configuration.
revalidateTag('products') clears the Data Cache entries tagged products.
3.3 Full Route Cache
Caches the entire rendered output of a route. Relates to static/ISR pages.
revalidatePath('/cart') marks that route as "stale" → it's rebuilt/re-rendered on the next request.
3.4 Router Cache (client-side)
When a user navigates with <Link>, Next.js can keep a client-side route snapshot for smooth back/forward.
→ Different from the server-side Data Cache; you rarely touch this directly.
Summary table - "which tool affects which layer?"
| Tool | Mainly affects |
|---|---|
fetch + revalidate / tags | Data Cache |
revalidateTag('x') | Data Cache (entries tagged x) |
revalidatePath('/path') | Full Route Cache + the route's RSC payload |
refresh() | Router / RSC tree of the current route (URL unchanged) |
cache: 'no-store' | Does not write to the Data Cache (always fetches fresh on render) |
export const dynamic / revalidate | Full Route Cache - static / ISR / dynamic (section 13) |
export const fetchCache | Default Data Cache behavior for every fetch in the segment |
4. fetch() options - cache, revalidate, tags
4.1 The three common modes
| Mode | Code | Behavior | Use when |
|---|---|---|---|
| Static / force-cache | default (in some cases) | Cached for a long time | Content rarely changes, not personalized |
| ISR | next: { revalidate: 60 } | Cached for 60s, then refreshed | Public catalog, blog |
| Dynamic | cache: 'no-store' | Every render = a fresh fetch | Cart, profile, per-user data |
ISR example (Nova - homepage featured products):
// getProducts(..., { authenticated: false })
await fetch(url, {
method: "GET",
next: {
tags: ["products", "catalog"],
revalidate: 60, // may be re-fetched after 60 seconds
},
});
Dynamic example (Nova - shopping cart):
await authFetch(`${apiUrl}/cart?userId=${userId}`, {
method: "GET",
cache: "no-store", // does NOT write to the Data Cache
next: { tags: ["cart", "cart-user-123"] },
});
4.2 What are tags? (labels attached to a fetch)
Tags are like stickers on a cache entry:
fetch("/api/products?page=1", { next: { tags: ["products"] } });
fetch("/api/products?page=2", { next: { tags: ["products"] } });
Then:
revalidateTag("products"); // clears EVERY fetch tagged "products" (every page, every query)
Nova defines these centrally in app/lib/cache-tags.ts:
export const CACHE_TAGS = {
products: "products",
catalog: "catalog",
product: (id) => `product-${id}`,
cart: "cart",
cartUser: (userId) => `cart-user-${userId}`,
user: "user",
userId: (id) => `user-${id}`,
};
4.3 no-store + tags together - seems contradictory?
cache: "no-store",
next: { tags: ["cart"] },
| Part | Meaning |
|---|---|
no-store | Every time the route is rendered → always calls the API fresh (never reads a stale Data Cache entry). |
tags | Still tags the entry so that revalidateTag("cart") can mark it stale + coordinate with revalidatePath / refresh. |
→ Tags on a no-store fetch do not replace revalidatePath + refresh for the cart; Nova still calls all three after a mutation (see section 8).
5. revalidateTag - clear cache by label
5.1 What does it do?
import { revalidateTag } from "next/cache";
revalidateTag("products");
→ Next.js invalidates every entry in the Data Cache tagged products.
5.2 Example timeline
T=0s User A opens the homepage → fetches featured products → cached 60s (tag: products)
T=10s User B adds to cart → addToCart() → revalidateTag("products")
T=11s User C opens the homepage → the "products" Data Cache is now stale → fresh API fetch
5.3 When it has NO effect
- A fetch using
cache: 'no-store'→ has no entry in the Data Cache →revalidateTaghas nothing to "clear" for that request. - Nova still calls
revalidateTagbecause:- Some fetches do use the cache with the same tag (catalog ISR on the homepage).
- It keeps the centralized invalidation strategy consistent.
5.4 Where does Nova call it?
app/lib/revalidate-shop.ts → revalidateAfterCartChange():
revalidateTag(CACHE_TAGS.cart);
revalidateTag(CACHE_TAGS.products);
revalidateTag(CACHE_TAGS.catalog);
if (userId) revalidateTag(CACHE_TAGS.cartUser(userId));
6. revalidatePath - force a route to re-render
6.1 What does it do?
import { revalidatePath } from "next/cache";
revalidatePath("/cart");
revalidatePath("/products", "layout");
revalidatePath("/", "layout");
→ Marks a route segment (and optionally the parent layout) as stale.
The next time a user (or the server) requests /cart → Next.js re-renders that route's Server Components → re-calling getCartSummary(), getProducts(), etc.
6.2 page vs layout
| Call | Meaning |
|---|---|
revalidatePath('/products') | Only the /products page |
revalidatePath('/products', 'layout') | The (shop) layout wrapping products and every child page (navbar may re-fetch too) |
Nova uses 'layout' for /products and / because the navbar / shell may depend on shared data.
6.3 Difference from revalidateTag
revalidateTag | revalidatePath | |
|---|---|---|
| Target | A fetch entry in the Data Cache | An already-rendered route (RSC tree) |
| Needs to know the API URL? | No - goes by tag | No - goes by app path |
Cart with no-store | Barely touches the Data Cache | Still needed - forces the cart page to re-render |
7. refresh() - refresh the current route
7.1 What does it do?
import { refresh } from "next/cache";
refresh();
→ Within the same request / Server Action, once the mutation is done, Next.js re-renders the Server Components on the route the user is currently viewing - without changing the URL, and without a full page reload like F5.
7.2 Visual comparison
| Action | URL changes? | Full reload? | Who triggers it? |
|---|---|---|---|
User clicks <Link> | Yes | No (client nav) | User |
router.refresh() (client) | No | No | Client |
refresh() (server, inside an Action) | No | No | Server Action |
revalidatePath | Not immediately | Not immediately | Server - takes effect on the next request |
| F5 | No | Yes | User |
7.3 refresh() vs revalidatePath
revalidatePath: marks the route cache - has strong effect for the next visit.refresh(): tries to update the current route's server UI immediately after an Action.
Nova calls both after addToCart so the user sees the latest data whether they're on /products or /cart.
7.4 Calling from the client
// app/lib/actions.ts
"use server";
export async function refreshShopData() {
refresh(); // via refreshShopRoute()
}
The client calls this when it needs a soft-refresh without a mutation that returns a full RSC payload.
7.5 Stripe webhook: refreshRoute: false
In checkout-sessions.ts, the webhook has no "user currently viewing a route":
revalidateAfterCartChange({ refreshRoute: false });
→ Only revalidateTag + revalidatePath; no refresh() (meaningless on a background job).
8. Why does Nova use all three?
This is the point that's easiest to confuse. Summarized in a table:
| Tool | Problem it solves |
|---|---|
revalidateTag | Homepage catalog ISR (revalidate: 60) would otherwise still show stale stock/price after a cart change / webhook |
revalidatePath | The /cart page and /products layout (navbar, list) need to re-run getCartSummary, getProducts |
refresh() | A user standing on /products after adding to cart sees the update immediately, without navigating |
In one sentence:
- Cached data (catalog) → needs
revalidateTag. no-storedata (cart) → needsrevalidatePath+refresh()to re-render.
9. Real-world flows in Nova Shop
9.1 Reading the product list (/products)
1. User opens /products?page=2&sort=price-low
2. products/page.tsx (Server) await searchParams
3. parseProductFilters() → a safe object
4. getProducts({ page: 2, sort: "price-low" }) [authenticated: true by default]
5. authFetch GET .../products?... with cache: "no-store" + tags
6. Returns ProductsPageResult → ListProductsComponent
Why no-store for the logged-in catalog?
The route is dynamic (cookie auth); each user/session should see fresh data on render, avoiding mixed cache across users on a shared Data Cache.
The featured homepage uses authenticated: false + revalidate: 60 → ISR, saving on API calls.
9.2 Adding to cart
1. productForm (client) calls addToCart()
2. cart.ts: authFetch POST /cart/add
3. revalidateCartCaches({ productId })
→ revalidateAfterCartChange({ userId, productId, refreshRoute: true })
4. Returns CartSummary to the client → updates UI + badge
9.3 Successful checkout (webhook)
1. Stripe POST /api/stripe/webhook
2. handleSuccessfulPayment()
3. revalidateAfterCartChange({ refreshRoute: false })
4. revalidateProductsCatalog({ refreshRoute: false })
10. authFetch & the service layer
10.1 authFetch (summary)
File: app/lib/api-client.ts
ensureValidAccessToken()
→ refreshes if the access token has expired
getAuthHeaders()
→ Authorization: Bearer + Cookie
fetch(url, init)
→ on 401: refreshTokens() + retry once
Always pass init cache/tags from the service - authFetch doesn't decide caching on its own:
authFetch(url, {
method: "GET",
cache: "no-store",
next: { tags: productTags },
});
10.2 Service layer
| File | Read function | Write function (mutation) |
|---|---|---|
services/products.ts | getProducts, getProductById | - |
services/cart.ts | getCartSummary | addToCart, updateCartItem, … |
services/user.ts | getUser | - |
revalidate-shop.ts | - | revalidateAfterCartChange (helper) |
Rule: Pages never call fetch directly; they only call a service.
10.3 Error handling
if (res.status === 401) unauthorized(); // redirect into the auth flow
if (res.status === 404) return EMPTY_CART; // empty cart
if (!res.ok) return EMPTY_RESULT; // catalog fallback
→ The page doesn't crash from a temporary API error (except for product detail, which throws).
10.4 parseProductFilters
searchParams from the URL are strings - parsed in one place:
const page = Math.max(1, Number(params.page) || 1);
Avoids NaN in pagination.
11. FAQ & common mistakes
Q1: I added to cart but /cart still shows stale data?
Check:
- Does
addToCartcallrevalidateAfterCartChange? - Does the cart page read
initialSummaryfrom the server once and then only useuseState- it may need to navigate again or rely onrefresh()/initialSummarychanging from the server. - The client cart-view updates local state from the Action's return value - that's fine; but you only see server data again after F5 reload if revalidation is missing.
Q2: Is revalidateTag alone enough?
No for a no-store cart. revalidatePath + refresh() are also needed.
Q3: Does revalidate: 60 mean the user always sees data that's up to 60 seconds stale?
Within 60 seconds, the cache may be used. After 60 seconds, the next request triggers revalidation.
Or immediately, if revalidateTag('products') is called (mutation/webhook).
Q4: Can getProducts be called from the client navbar?
Yes (as a Server Action), but it's not recommended - it adds a round-trip. Better to fetch in the server layout and pass props down to the navbar.
Q5: What's the difference between router.refresh() (client) and refresh() (server)?
They both aim to "refresh the RSC." Nova exposes a refreshShopData() Server Action so the client can trigger refresh() from a server context.
Q6: Does the NEXT_PUBLIC_EXTERNAL_API_URL env var leak any secret?
Only the public URL. The token lives in an HttpOnly cookie, readable only by the server through authFetch.
Q7: How is segment config different from fetch({ cache: 'no-store' })?
Segment config (export const in page.tsx) | fetch() options | |
|---|---|---|
| Scope | The whole route segment (folder + child layouts) | Each individual API call |
| Who reads it | Next.js at build time and during routing | Next.js when fetch executes inside a component |
| Main effect | Static vs dynamic route, Full Route Cache, default fetch behavior in the segment | Data Cache (whether the API response is stored or not) |
| Nova example | export const dynamic = 'force-dynamic' on /cart | cache: 'no-store' in getCartSummary() |
Both are needed when a route must always render per-request (segment) and each API call must not be cached across users (fetch). Setting only the fetch option without the segment config → the route can still be treated as static/ISR and the HTML + RSC payload gets cached (layer 3).
Q8: Why not import dynamic from segment-config.ts?
Next.js statically analyzes the route file at build time - it only accepts literals (export const dynamic = "force-dynamic"). Re-exporting from another module → the build warns and ignores the config. The app/lib/segment-config.ts file is only a reference to keep the value in sync across the team.
12. File map in the repo
app/lib/
├── cache-tags.ts # Tag constants
├── revalidate-shop.ts # revalidateTag + revalidatePath + refresh()
├── api-client.ts # authFetch
└── services/
├── products.ts # getProducts (ISR vs no-store)
├── cart.ts # cart + revalidate after mutation
└── user.ts # getUser (no-store + tags)
app/lib/actions.ts # refreshShopData() → refresh()
app/lib/checkout-sessions.ts # webhook → revalidate (no refresh)
app/(shop)/products/page.tsx # getProducts()
app/ui/shop/storefront-hero.tsx # getProducts({ authenticated: false })
app/lib/segment-config.ts # Reference values (literals must be written in each page)
app/lib/services/products.ts # getAllProductSlugParams() - build + sitemap
13. Route Segment Config - role & relationship to cache
Docs: Route Segment Config
13.1 What is segment config?
In the App Router, every folder (app/, app/(shop)/, app/(shop)/products/, …) is a route segment. You declare that segment's behavior by exporting constants directly in a convention file:
// app/(shop)/cart/page.tsx
export const dynamic = "force-dynamic";
export const fetchCache = "default-no-store";
export default async function CartPage() { /* ... */ }
These are not React props and are not passed down to child components - Next.js reads them at build time and while handling a request to decide how that segment is rendered/cached.
Segment config answers the question: "Is this /cart route a pre-built static page, ISR, or must it be re-run on every request?"
13.2 Role - why is this needed on top of fetch()?
Section 3 covers the 4 cache layers. Segment config mainly controls layer 3 (Full Route Cache) and the default behavior for fetches within the segment - it does not replace revalidateTag / revalidatePath / refresh().
| Layer | Tool | Role |
|---|---|---|
| Declaration (before runtime) | Segment config | Whether this route is static, ISR, or dynamic; Node/Edge runtime |
| Fetching data (during render) | fetch / authFetch | Whether each API response is cached or not |
| After data changes | revalidateTag, revalidatePath, refresh | Clears the old cache, forces a re-render |
A simple example:
- Only
fetch(..., { cache: 'no-store' })ingetCartSummary()→ the API response doesn't go into the Data Cache, but Next.js can still cache the rendered output of/cart(HTML + RSC) if the segment is treated as static. - Adding
export const dynamic = 'force-dynamic'oncart/page.tsx→ the segment always renders per request → matches each user's cart.
→ Segment config = "the rules of the whole page"; fetch options = "the rules of each individual API call."
13.3 Options Nova uses - what each one means
| Option | Nova's value | Effect |
|---|---|---|
revalidate | 60 on / | ISR segment: regenerates the route at most every 60s (matches featured products). Not used on auth pages. |
dynamic | 'force-dynamic' on shop/auth/checkout | Does not build static HTML for the segment; every request renders fresh on the server. Needed whenever cookies(), headers(), searchParams, or session are used. |
fetchCache | 'default-no-store' on shop | Fetches in the segment default to not writing to the Data Cache (complements cache: 'no-store' in the service). |
runtime | 'nodejs' on the Stripe/Auth API | The Route Handler runs on Node.js (Stripe SDK, crypto, raw webhook body). |
dynamic - common values:
| Value | Behavior |
|---|---|
'auto' (default) | Static if no dynamic APIs are used; dynamic if cookies() / searchParams / … are used |
'force-static' | Forces static (rarely used for a shop with login) |
'force-dynamic' | Always dynamic - Nova uses this for /products, /cart, … |
'error' | Build fails if a dynamic API is detected |
Nova chooses explicit force-dynamic on auth routes instead of 'auto' to avoid confusion during refactors (temporarily removing cookies() → the route accidentally becomes static).
13.4 Segment config vs revalidateTag / revalidatePath / refresh
| Segment config | revalidateTag / revalidatePath / refresh | |
|---|---|---|
| When it applies | Every request / build (a fixed rule) | After an event (add to cart, Stripe webhook, …) |
| Who calls it | Developer declares it in the route file | Runtime code (revalidate-shop.ts, actions) |
| How it changes | Deploying new code | A one-time invalidation of the existing cache |
They complement each other:
force-dynamicsegment +fetch no-store→ baseline: data and route are always "fresh" on render.- User adds to cart →
revalidateTag+revalidatePath+refresh()→ clears any lingering cache, updates the UI immediately without an F5.
13.5 Nova Shop - two presets
Preset A - Public catalog (homepage /)
// app/page.tsx
export const revalidate = 60;
// getProducts({ authenticated: false }) in storefront-hero
fetch(url, { next: { revalidate: 60, tags: ["products", "catalog"] } });
- Segment ISR 60s + Data Cache ISR 60s → matching TTL, fast homepage load, catalog doesn't require login.
- After an admin edits a product / a webhook fires:
revalidateTag('products')is still needed - segment config doesn't automatically know data has changed.
Preset B - Auth / per-user (shop, cart, checkout success)
export const dynamic = "force-dynamic";
export const fetchCache = "default-no-store";
await authFetch(url, { cache: "no-store", next: { tags: [...] } });
- The segment isn't static + the API isn't cached across users.
(shop)/layout.tsxsets preset B → every child page (/products,/cart, …) inherits it - no need to repeat it on everypage.tsx(Nova still re-exports it on the page to make the intent clear when reading the file).
Preset C - API Route Handler (Stripe, NextAuth)
export const dynamic = "force-dynamic";
export const runtime = "nodejs";
- Each POST is its own request; Stripe needs the Node runtime.
13.6 Inheritance from layout to page
Config on a parent layout applies to the whole child segment tree, unless a child page overrides it:
app/(shop)/layout.tsx → dynamic + fetchCache
└── products/page.tsx → (same, or overridden)
└── cart/page.tsx
The practical rule: the "most dynamic" segment wins - a force-dynamic child isn't "pulled back" by a static layout.
13.7 The literal-value rule (Next.js build)
// ❌ Build ignores this - Next can't statically analyze it
export { dynamic } from "@/app/lib/segment-config";
// ✅
export const dynamic = "force-dynamic";
The app/lib/segment-config.ts file records constants like CATALOG_REVALIDATE_SECONDS, AUTH_DYNAMIC, … purely to keep docs and code review in sync - the actual value still has to be copied as a literal into each page.tsx / layout.tsx / route.ts.
13.8 Segment config table for each Nova page
| Route / file | revalidate | dynamic | fetchCache | runtime | Reason |
|---|---|---|---|---|---|
/ - app/page.tsx | 60 | - | - | - | Public landing; FeaturedProducts ISR |
(shop)/layout.tsx | - | force-dynamic | default-no-store | - | Whole shop is auth/cookies-based |
/products - (shop)/products/page.tsx | - | ✓ | ✓ | - | authFetch, searchParams |
/products/[slug] | 60 | - | - | - | ISR + generateStaticParams; runtime auth via getCatalogAuthenticated |
/cart | - | ✓ | ✓ | - | getCartSummary per user |
/customers | - | ✓ | ✓ | - | Route protected by middleware |
/login | - | ✓ | ✓ | - | Session redirect |
checkout/layout.tsx | - | ✓ | ✓ | - | Success page reads Stripe + searchParams |
/checkout/success | - | ✓ | ✓ | - | retrieveCheckoutSession |
/checkout/cancel | - | ✓ | ✓ | - | Shares the checkout layout |
POST /api/checkout | - | ✓ | - | nodejs | Stripe SDK |
POST /api/checkout/cart | - | ✓ | - | nodejs | Stripe + cart |
POST /api/stripe/webhook | - | ✓ | - | nodejs | Webhook raw body |
/api/auth/[...nextauth] | - | ✓ | - | nodejs | NextAuth handlers |
✓ = export const dynamic = "force-dynamic" and fetchCache = "default-no-store" (literal in the file).
Appendix: fetch options quick reference
| Option | Data Cache | When to use |
|---|---|---|
| (default static) | Yes, long-lived | Rarely used for dynamic APIs |
next: { revalidate: N } | Yes, TTL of N seconds | Public catalog |
cache: 'no-store' | No | Cart, user, auth API |
next: { tags: [...] } | Tagged | Combined with revalidateTag |
revalidateTag('x') | Invalidate by tag | After a mutation / webhook |
revalidatePath('/p') | Invalidate the route | After a mutation |
refresh() | Re-renders the current route | Inside a Server Action |
export const dynamic | Full Route Cache / static vs dynamic | Section 13 |
export const revalidate | ISR for the whole segment | Home / = 60 |
export const fetchCache | Default fetch behavior in the segment | Shop = default-no-store |
export const runtime | Node vs Edge for a Route Handler | Stripe API = nodejs |
Self-check exercises
- Redraw the sequence diagram: a user adds to cart while on
/products- point out exactly which step uses tag, path, and refresh. - Why do the homepage's featured products use
authenticated: false+revalidate: 60? - Why does the Stripe webhook use
refreshRoute: false? - If
revalidatePath("/cart")were removed fromrevalidateAfterCartChange, what could happen when a user opens/cartafter adding an item on another page? - If only
cache: 'no-store'is used ingetCartSummary()withoutexport const dynamic = 'force-dynamic'on/cart- how might the Full Route Cache be affected?
Suggested answers:
- Action → API →
revalidateTag(catalog cache) →revalidatePath(cart, products layout) →refresh()(current route) → client receivesCartSummary. - The homepage can be public/ISR; no token is required; it reduces API load.
- A webhook has no "current browser route."
- The cart page might still render with a stale RSC payload until a navigation/reload happens - even though the fetch itself uses
no-storeand fetches fresh whenever the route is actually re-rendered. - The API response is always fresh on render, but the route's output (RSC/HTML) can still be cached if the segment is static - the user might see a stale cart until they navigate/the path is revalidated; that's why Nova sets
force-dynamicon the segment.
See also: 3. Server Actions · 0. Best Practices vs Nova
