Skip to content

feat(examples): add Next.js App Router optimistic updates example - #10997

Merged
TkDodo merged 7 commits into
TanStack:mainfrom
guirab:feat/nextjs-app-optimistic-updates
Aug 18, 2026
Merged

feat(examples): add Next.js App Router optimistic updates example#10997
TkDodo merged 7 commits into
TanStack:mainfrom
guirab:feat/nextjs-app-optimistic-updates

Conversation

@guirab

@guirab guirab commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

🎯 Changes

Adds a new example demonstrating optimistic updates with Next.js App Router (App Directory).

This example was missing from the repo — the existing Next.js examples only cover prefetching
and streaming. Optimistic updates with App Router is a common real-world pattern that many
users ask about in discussions.

The example shows both approaches supported by TanStack Query v5:

  1. Via UI variables (TodoListUI.tsx) — renders the pending item directly from
    mutation.variables, simpler and requires no cache manipulation or rollback logic.

  2. Via cache manipulation (TodoListCache.tsx) — uses onMutate to update the cache
    optimistically, with typed context and automatic rollback via onError if the mutation fails.

A tab switcher lets users compare both approaches side by side.

The API route simulates ~30% random failure to demonstrate the rollback behavior.
Implementation follows the conventions of nextjs-app-prefetching.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test:pr.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is docs/CI/dev-only (no release).

Summary by CodeRabbit

  • New Features

    • Added a Next.js todo example demonstrating UI-based and cache-based optimistic updates.
    • Added tabbed navigation, validation, loading states, error handling, rollback, and automatic synchronization.
    • Added simulated server delays and intermittent failures for demonstrating recovery behavior.
  • Documentation

    • Added setup instructions and explanations of optimistic updates and rollback behavior.
    • Consolidated optimistic-update example navigation under the new Next.js example.
  • Chores

    • Removed the superseded optimistic-update examples.

@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d6df766d-e7a0-45ab-b91f-d41687c69504

📥 Commits

Reviewing files that changed from the base of the PR and between 273db38 and f40a0b5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • docs/config.json
  • examples/react/nextjs-app-optimistic-updates/README.md
  • examples/react/nextjs-app-optimistic-updates/app/api/todos/data.ts
  • examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts
  • examples/react/nextjs-app-optimistic-updates/components/ApproachTabs.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListCache.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListUI.tsx
  • examples/react/nextjs-app-optimistic-updates/package.json
  • examples/react/optimistic-updates-cache/README.md
  • examples/react/optimistic-updates-cache/next.config.js
  • examples/react/optimistic-updates-cache/package.json
  • examples/react/optimistic-updates-cache/src/pages/api/data.ts
  • examples/react/optimistic-updates-cache/src/pages/index.tsx
  • examples/react/optimistic-updates-cache/tsconfig.json
  • examples/react/optimistic-updates-ui/.gitignore
  • examples/react/optimistic-updates-ui/README.md
  • examples/react/optimistic-updates-ui/next.config.js
  • examples/react/optimistic-updates-ui/src/pages/api/data.ts
  • examples/react/optimistic-updates-ui/src/pages/index.tsx
💤 Files with no reviewable changes (11)
  • examples/react/optimistic-updates-ui/README.md
  • examples/react/optimistic-updates-cache/README.md
  • examples/react/optimistic-updates-ui/.gitignore
  • examples/react/optimistic-updates-cache/tsconfig.json
  • examples/react/optimistic-updates-ui/src/pages/api/data.ts
  • examples/react/optimistic-updates-ui/src/pages/index.tsx
  • examples/react/optimistic-updates-cache/next.config.js
  • examples/react/optimistic-updates-cache/package.json
  • examples/react/optimistic-updates-cache/src/pages/index.tsx
  • examples/react/optimistic-updates-cache/src/pages/api/data.ts
  • examples/react/optimistic-updates-ui/next.config.js
🚧 Files skipped from review as they are similar to previous changes (6)
  • examples/react/nextjs-app-optimistic-updates/package.json
  • examples/react/nextjs-app-optimistic-updates/components/ApproachTabs.tsx
  • examples/react/nextjs-app-optimistic-updates/README.md
  • examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts
  • examples/react/nextjs-app-optimistic-updates/components/TodoListUI.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListCache.tsx

Included review availability: Your plan includes up to 10 reviews per rolling hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Adds a runnable Next.js App Router example with an in-memory todos API, React Query server hydration, and two client-side optimistic update approaches: mutation variables and cache manipulation with rollback.

Changes

Next.js optimistic updates example

Layer / File(s) Summary
Project configuration and tooling
examples/react/nextjs-app-optimistic-updates/package.json, tsconfig.json, next.config.js, .gitignore, README.md, docs/config.json
Adds project metadata, TypeScript and Next.js settings, ignore patterns, documentation, and navigation for the consolidated example.
In-memory todos API
examples/react/nextjs-app-optimistic-updates/app/api/todos/*
Defines the todo data shape and in-memory store. Adds GET and POST handlers with validation, delays, randomized failures, and persistence.
Query client and application shell
examples/react/nextjs-app-optimistic-updates/app/get-query-client.ts, app/providers.tsx, app/layout.tsx
Adds server and browser QueryClient handling, React Query providers, devtools, metadata, and the root layout.
Server prefetch and hydration
examples/react/nextjs-app-optimistic-updates/app/page.tsx
Prefetches ['todos'] on the server and hydrates ApproachTabs with the prefetched state.
Optimistic update interfaces
examples/react/nextjs-app-optimistic-updates/components/ApproachTabs.tsx, components/TodoListUI.tsx, components/TodoListCache.tsx
Adds tab switching, pending-item rendering from mutation.variables, and cache updates with snapshot restore, optimistic-item removal, error reporting, and settled-query invalidation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to f40a0

This documentation/example-only change introduces no actionable merge-blocking risk at the current head and is merge-ready after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant TodoListCache
  participant RouteHandler
  participant TodoStore

  User->>TodoListCache: Submit todo
  TodoListCache->>TodoListCache: Snapshot and update cache
  TodoListCache->>RouteHandler: POST /api/todos
  RouteHandler->>TodoStore: Store todo on success
  RouteHandler-->>TodoListCache: Return success or error
  TodoListCache->>TodoListCache: Restore on error or invalidate on settle
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the addition of a Next.js App Router optimistic updates example.
Description check ✅ Passed The description covers the changes, motivation, implementation approaches, testing checklist, and release impact.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@socket-security

socket-security Bot commented Jun 28, 2026

Copy link
Copy Markdown

No dependency changes detected. Learn more about Socket for GitHub.

👍 No dependency changes detected in pull request

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/react/nextjs-app-optimistic-updates/.eslintrc.cjs`:
- Around line 1-9: The current ESLint config only enables React rules, so the
example’s TypeScript `.tsx` files may fail to parse before linting runs. Update
the configuration in the `.eslintrc.cjs` for the Next.js optimistic updates
example by extending the Next.js TypeScript preset, using `next/core-web-vitals`
and `next/typescript` (or equivalent TypeScript parser setup) alongside the
existing React support. Make sure the config still covers the example’s React
and hooks rules while correctly handling TypeScript sources.

In `@examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts`:
- Around line 24-44: The POST handler in the todos route accepts unvalidated
input, so invalid or whitespace-only text can be pushed into the shared todos
array. Update POST to validate the parsed request body before creating newTodo,
using the existing request/json flow to reject missing, non-string, or
trimmed-empty text. Return a 400 response for invalid input and only call
todos.push after the text has been sanitized and confirmed valid.
- Around line 9-18: Move the runtime data and helper out of the route handler
module: `todos` and `getTodos` in `app/api/todos/route.ts` should be relocated
to a sibling module and imported where needed. Keep `route.ts` limited to HTTP
handlers/config exports only, and reference the shared module from the route
handler so Next’s route export rules are satisfied.

In `@examples/react/nextjs-app-optimistic-updates/components/TodoListCache.tsx`:
- Around line 42-65: The optimistic update flow in TodoListCache’s
onMutate/onError leaves the optimistic todo visible when previousTodos is
undefined, so track the created optimistic item’s id in the mutation context and
use it in onError to remove that specific row directly when there is no
snapshot. Keep the existing rollback to queryClient.setQueryData for the
previousTodos path, but in the no-snapshot path ensure the optimistic entry
added by queryClient.setQueryData is deleted immediately rather than waiting for
the refetch.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9ed6d652-181d-48d6-86e8-8c6367564728

📥 Commits

Reviewing files that changed from the base of the PR and between 610e8d1 and 00a4652.

📒 Files selected for processing (14)
  • examples/react/nextjs-app-optimistic-updates/.eslintrc.cjs
  • examples/react/nextjs-app-optimistic-updates/.gitignore
  • examples/react/nextjs-app-optimistic-updates/README.md
  • examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts
  • examples/react/nextjs-app-optimistic-updates/app/get-query-client.ts
  • examples/react/nextjs-app-optimistic-updates/app/layout.tsx
  • examples/react/nextjs-app-optimistic-updates/app/page.tsx
  • examples/react/nextjs-app-optimistic-updates/app/providers.tsx
  • examples/react/nextjs-app-optimistic-updates/components/ApproachTabs.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListCache.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListUI.tsx
  • examples/react/nextjs-app-optimistic-updates/next.config.js
  • examples/react/nextjs-app-optimistic-updates/package.json
  • examples/react/nextjs-app-optimistic-updates/tsconfig.json

Comment on lines +1 to +9
/** @type {import('eslint').Linter.Config} */
module.exports = {
extends: ['plugin:react/jsx-runtime', 'plugin:react-hooks/recommended'],
settings: {
react: {
version: 'detect',
},
},
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use the Next TypeScript ESLint preset here examples/react/nextjs-app-optimistic-updates/.eslintrc.cjs only enables React rules, so the example’s .tsx files can hit parse errors before any lint rules run. Extend next/core-web-vitals + next/typescript (or add @typescript-eslint/parser) so the TypeScript sources are linted.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/react/nextjs-app-optimistic-updates/.eslintrc.cjs` around lines 1 -
9, The current ESLint config only enables React rules, so the example’s
TypeScript `.tsx` files may fail to parse before linting runs. Update the
configuration in the `.eslintrc.cjs` for the Next.js optimistic updates example
by extending the Next.js TypeScript preset, using `next/core-web-vitals` and
`next/typescript` (or equivalent TypeScript parser setup) alongside the existing
React support. Make sure the config still covers the example’s React and hooks
rules while correctly handling TypeScript sources.

Comment thread examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts Outdated
Comment thread examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts (1)

8-20: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Handle malformed JSON bodies explicitly.

request.json() can throw before the text validation runs, so an invalid POST still turns into a 500 instead of the 400 { error } contract your client code expects.

Suggested fix
 export async function POST(request: Request) {
-  const body = (await request.json()) as unknown
+  let body: unknown
+  try {
+    body = await request.json()
+  } catch {
+    return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })
+  }
 
   const text =
     body !== null &&
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts` around
lines 8 - 20, The POST handler currently assumes request.json() always succeeds,
so malformed JSON can escape the text validation and return a 500 instead of the
expected 400 response. Update POST in the todos route to catch JSON parsing
failures around request.json(), and return the same NextResponse.json({ error:
'text is required' }, { status: 400 }) contract when the body cannot be parsed
or is invalid, keeping the existing text extraction/validation logic intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts`:
- Around line 8-20: The POST handler currently assumes request.json() always
succeeds, so malformed JSON can escape the text validation and return a 500
instead of the expected 400 response. Update POST in the todos route to catch
JSON parsing failures around request.json(), and return the same
NextResponse.json({ error: 'text is required' }, { status: 400 }) contract when
the body cannot be parsed or is invalid, keeping the existing text
extraction/validation logic intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: db5f2199-d0de-4a40-a664-fc0e657b95e0

📥 Commits

Reviewing files that changed from the base of the PR and between 00a4652 and 7ad24c1.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • examples/react/nextjs-app-optimistic-updates/app/api/todos/data.ts
  • examples/react/nextjs-app-optimistic-updates/app/api/todos/route.ts
  • examples/react/nextjs-app-optimistic-updates/app/page.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListCache.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListUI.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • examples/react/nextjs-app-optimistic-updates/app/page.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListCache.tsx
  • examples/react/nextjs-app-optimistic-updates/components/TodoListUI.tsx

@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@nx-cloud

nx-cloud Bot commented Aug 18, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 8f010b7

Command Status Duration Result
nx affected --targets=test:sherif,test:knip,tes... ✅ Succeeded 4m 57s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded <1s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-18 07:55:47 UTC

@pkg-pr-new

pkg-pr-new Bot commented Aug 18, 2026

Copy link
Copy Markdown
More templates

@tanstack/angular-query-experimental

npm i https://pkg.pr.new/@tanstack/angular-query-experimental@10997

@tanstack/eslint-plugin-query

npm i https://pkg.pr.new/@tanstack/eslint-plugin-query@10997

@tanstack/lit-query

npm i https://pkg.pr.new/@tanstack/lit-query@10997

@tanstack/preact-query

npm i https://pkg.pr.new/@tanstack/preact-query@10997

@tanstack/preact-query-devtools

npm i https://pkg.pr.new/@tanstack/preact-query-devtools@10997

@tanstack/preact-query-persist-client

npm i https://pkg.pr.new/@tanstack/preact-query-persist-client@10997

@tanstack/query-async-storage-persister

npm i https://pkg.pr.new/@tanstack/query-async-storage-persister@10997

@tanstack/query-broadcast-client-experimental

npm i https://pkg.pr.new/@tanstack/query-broadcast-client-experimental@10997

@tanstack/query-core

npm i https://pkg.pr.new/@tanstack/query-core@10997

@tanstack/query-devtools

npm i https://pkg.pr.new/@tanstack/query-devtools@10997

@tanstack/query-persist-client-core

npm i https://pkg.pr.new/@tanstack/query-persist-client-core@10997

@tanstack/query-sync-storage-persister

npm i https://pkg.pr.new/@tanstack/query-sync-storage-persister@10997

@tanstack/react-query

npm i https://pkg.pr.new/@tanstack/react-query@10997

@tanstack/react-query-devtools

npm i https://pkg.pr.new/@tanstack/react-query-devtools@10997

@tanstack/react-query-next-experimental

npm i https://pkg.pr.new/@tanstack/react-query-next-experimental@10997

@tanstack/react-query-persist-client

npm i https://pkg.pr.new/@tanstack/react-query-persist-client@10997

@tanstack/solid-query

npm i https://pkg.pr.new/@tanstack/solid-query@10997

@tanstack/solid-query-devtools

npm i https://pkg.pr.new/@tanstack/solid-query-devtools@10997

@tanstack/solid-query-persist-client

npm i https://pkg.pr.new/@tanstack/solid-query-persist-client@10997

@tanstack/svelte-query

npm i https://pkg.pr.new/@tanstack/svelte-query@10997

@tanstack/svelte-query-devtools

npm i https://pkg.pr.new/@tanstack/svelte-query-devtools@10997

@tanstack/svelte-query-persist-client

npm i https://pkg.pr.new/@tanstack/svelte-query-persist-client@10997

@tanstack/vue-query

npm i https://pkg.pr.new/@tanstack/vue-query@10997

@tanstack/vue-query-devtools

npm i https://pkg.pr.new/@tanstack/vue-query-devtools@10997

commit: f40a0b5

@TkDodo
TkDodo merged commit baa256c into TanStack:main Aug 18, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants