Skip to content

feat!: improve Next.js App Router fidelity#38

Draft
kasperpeulen wants to merge 187 commits into
mainfrom
codex/next-fidelity-transforms-pr36
Draft

feat!: improve Next.js App Router fidelity#38
kasperpeulen wants to merge 187 commits into
mainfrom
codex/next-fidelity-transforms-pr36

Conversation

@kasperpeulen

@kasperpeulen kasperpeulen commented May 14, 2026

Copy link
Copy Markdown
Member

Summary

This PR makes vitest-plugin-rsc much more useful for testing real Next.js App Router code in Vitest Browser Mode.

Users can now test pages, layouts, Server Actions, redirects, cache updates, request state, fonts, images, metadata, and global CSS with fast Vitest browser tests instead of reaching for full E2E coverage for every App Router scenario.

🧭 App Router Feature Matrix

Page Router features are intentionally ignored.

Legend: ✅ supported · 🧪 partial/experimental · 🧩 mock/shim · ⚠️ not claimed · 🚫 unsupported

🧬 RSC Runtime

  • Current plugin: ✅ Generic RSC loop in Vitest Browser Mode: Server Component → Flight → Client Component hydration → Server Action → rerender.
  • This PR: ✅ Keeps that base runtime, but routes real Next App Router renders through Next app-render.
  • Storybook nextjs-vite: 🧪 Experimental browser RSC wrapper for stories; server-side resources usually still need mocks.

🛣️ Real App Routes By URL

  • Current plugin: 🧪 Route-aware direct component renders with url and route, but no full app route discovery/render pipeline.
  • This PR:renderServer({ url }) discovers real app/ routes and renders them through Next loader trees and app-render.
  • Storybook nextjs-vite: 🚫 Component stories only; it does not render real app routes by URL.

🧱 Layout Tree Fidelity

  • Current plugin: ⚠️ Partial route context, not a full Next loader tree or document render.
  • This PR: ✅ Route groups, nested layouts, templates, dynamic/catch-all/optional segments, and parallel/default slots.
  • Storybook nextjs-vite: 🧩 Manual App Router context from story parameters.

🧭 Navigation And Links

  • Current plugin:next/navigation hooks and next/link work in route-aware component tests.
  • This PR: ✅ Client navigation updates the real browser location and renders target-route UI.
  • Storybook nextjs-vite: 🧩 Router/navigation APIs are mocked and logged as Storybook actions.

📨 Server Actions

  • Current plugin: ✅ Direct action-and-rerender tests, plus MSW basics for request-shaped flows.
  • This PR: ✅ Next action response protocol, redirects, thrown errors, missing actions, and access fallbacks.
  • Storybook nextjs-vite: 🚫 Docs call Server Actions future work.

🍪 Headers, Cookies, And Draft Mode

  • Current plugin: ✅ Request context for direct RSC/action tests.
  • This PR: ✅ Real Next request stores inside route renders and Server Actions.
  • Storybook nextjs-vite: 🧩 Writable mocks for headers(), cookies(), and draftMode().

♻️ Cache And Revalidation

  • Current plugin:refresh, tags, cached fetch, and action rerenders in focused tests.
  • This PR: ✅ App-render cache wiring, use cache, cache handlers, cache life profiles, and invalidation coverage.
  • Storybook nextjs-vite: 🧩 next/cache exports are mock functions, not the real cache runtime.

🚦 next.config Routes

  • Current plugin: ⚠️ Limited or no real request routing through app route matching.
  • This PR: ✅ Headers, redirects, and rewrites are applied before app route resolution.
  • Storybook nextjs-vite: 🧪 Loads Next config for env/build conveniences, but not as route-render behavior.

🧱 Middleware, Proxy, And Request Pipeline

  • Current plugin: 🚫 No middleware/proxy execution.
  • This PR: 🚫 Explicit non-goal. next.config headers/redirects/rewrites are supported, but middleware/proxy files are not executed.
  • Storybook nextjs-vite: 🚫 No real Next request pipeline; component stories run in an isolated preview.

🖼️ Images

  • Current plugin: ⚠️ Not a claimed Next image fidelity surface.
  • This PR: ✅ Real next/image client boundary, getImageProps, static imports through Next image loader, blur metadata, static media URLs, and preload behavior.
  • Storybook nextjs-vite: ✅ Good component-story support, but static image metadata is implemented through Storybook/Vite-side image handling.

🔤 Fonts

  • Current plugin: ⚠️ Not a claimed Next font fidelity surface.
  • This PR: ✅ Next SWC font transform, real Google/local font loaders, generated CSS, static media URLs, and route-scoped preload manifest.
  • Storybook nextjs-vite: 🧪 Partial support; docs list unsupported font config, fallback, adjust, preload, and display behavior.

🧾 Metadata And Metadata Routes

  • Current plugin: ⚠️ Partial head path, not full App Router metadata fidelity.
  • This PR:metadata, generateMetadata, viewport, robots, sitemap, manifest, static metadata images, and dynamic OG/Twitter images.
  • Storybook nextjs-vite: ⚠️ next/head works in stories, but App Router metadata route fidelity is not claimed.

🎨 Global Styling And Tailwind

  • Current plugin: ✅ Vite/global CSS can be imported in setup/app code.
  • This PR: ✅ Global CSS/Tailwind survives Next document hydration, and next/font styles/preloads are covered.
  • Storybook nextjs-vite: ✅ Strong support for global CSS, Tailwind/PostCSS, and component-story styling setup.

💅 CSS Modules, Sass, And Styled JSX

  • Current plugin: ⚠️ Not covered as a Next styling fidelity surface.
  • This PR: ⚠️ Not claimed yet. styled-jsx especially still needs explicit Next SWC + server-inserted style coverage before we should claim it.
  • Storybook nextjs-vite: ✅ Documents CSS Modules, Sass/SCSS, and styled-jsx support.

🧪 Route Handlers

  • Current plugin: ⚠️ App route handlers exist as app code, but are not a first-class render target.
  • This PR: ✅ Direct imports cover NextRequest, NextResponse, cookies, nextUrl, methods, streams, redirects/rewrites, and ImageResponse. They are still not renderServer({ url }) targets.
  • Storybook nextjs-vite: ⚠️ Not the focus of Storybook component isolation.

🏗️ Production And App Lifecycle Fidelity

  • Current plugin: 🚫 Not claimed.
  • This PR: 🚫 Production build output, PPR/progressive timing fidelity, instrumentation startup lifecycles, and full Node runtime parity remain out of scope.
  • Storybook nextjs-vite: 🚫 Optimized for Storybook preview/build, not Next production server fidelity.

🧾 Quick Read

  • Current plugin: Good for focused App Router-ish RSC component/action tests.
  • This PR: A much higher-fidelity Next App Router test runtime, while still explicit about non-goals.
  • Storybook nextjs-vite: Stronger for isolated component authoring and styling conveniences, but mostly mocks App Router runtime behavior.

What This Unlocks

Render a real app route

Test the actual page from your app/ directory by URL. Layouts, route groups, templates, parallel routes, metadata, CSS, and route-level states are included in the render.

import { expect, test } from "vitest";
import { page } from "vitest/browser";
import { renderServer } from "vitest-plugin-rsc/nextjs/testing-library";

test("renders the notes page", async () => {
  await renderServer({ url: "/notes" });

  await expect.element(page.getByRole("heading", { name: "Notes" })).toBeVisible();
});

Test a component with route state

For smaller tests, render one component but still give it the route params and search params it expects.

await renderServer(<NoteToolbar />, {
  url: "/notes/123?tab=activity",
  route: "/notes/[id]",
});

await expect.element(page.getByText("note id: 123")).toBeVisible();
await expect.element(page.getByText("tab: activity")).toBeVisible();

Test Server Actions from the hydrated UI

Click buttons and submit forms. Actions can update data, set cookies, refresh the current route, redirect, or revalidate cached data.

await renderServer({ url: "/notes/new" });

await page.getByLabelText("Title").fill("Inbox triage");
await page.getByRole("button", { name: "Create note" }).click();

await expect.element(page.getByText("Inbox triage")).toBeVisible();

Assert real client navigation and redirects

Code using next/navigation and next/link can be tested through normal browser interactions. After a client-side navigation or same-origin Server Action redirect, assert window.location and the new UI.

import { expect, vi } from "vitest";

await renderServer({ url: "/route-action" });
await page.getByRole("button", { name: "Redirect route action" }).click();

await vi.waitFor(() => {
  expect(window.location.pathname).toBe("/route-patterns/conventions");
  expect(window.location.search).toBe("?from=route-action");
});
await expect.element(page.getByRole("heading", { name: "Route conventions" })).toBeVisible();

Use next.config behavior in tests

Route tests can exercise configured headers, redirects, and rewrites.

const result = await renderServer({ url: "/start" });

expect(result.headers.get("x-notes-route")).toBe("enabled");
await expect.element(page.getByRole("heading", { name: "Notes" })).toBeVisible();

Test cache and revalidation flows

App code can use cached fetch, unstable_cache, use cache, updateTag, revalidateTag, and revalidatePath, while tests assert the user-visible result after an action.

await renderServer({ url: "/notes" });

await expect.element(page.getByText("notes: 0")).toBeVisible();
await page.getByRole("button", { name: "Create note" }).click();
await expect.element(page.getByText("notes: 1")).toBeVisible();

Cover fonts, images, CSS, and metadata

Pages using next/font, next/image, static image imports, app/globals.css, metadata exports, and metadata routes can be rendered and asserted in browser tests.

await renderServer({ url: "/marketing" });

await expect.element(page.getByAltText("Acme logo")).toBeVisible();
expect(document.title).toBe("Marketing");
expect(getComputedStyle(document.documentElement).getPropertyValue("--background")).not.toBe("");
expect(document.head.querySelector('link[rel="preload"][as="font"]')).not.toBeNull();

Test route handlers directly

Route handlers can be imported directly for focused tests around NextRequest, NextResponse, cookies, redirects, rewrites, streams, and ImageResponse.

Documentation

The README now has user-facing setup and usage examples for the Next.js App Router helpers. The maintainer-facing architecture notes live separately in docs/new-architecture.md.

Validation

CI covers format, lint, typecheck, build, Vitest, preview package publication, and the Next.js 16.0, 16.1, latest, and canary compatibility matrix.

@pkg-pr-new

pkg-pr-new Bot commented May 14, 2026

Copy link
Copy Markdown
pnpm add https://pkg.pr.new/vitest-plugin-rsc@38 -D

commit: 0a19ebc

@kasperpeulen kasperpeulen force-pushed the codex/next-fidelity-transforms-pr36 branch from 1b8cc3a to dd360ce Compare May 15, 2026 02:32
@kasperpeulen kasperpeulen changed the base branch from codex/split-next-plugin-internals to main May 15, 2026 03:17
@kasperpeulen kasperpeulen force-pushed the codex/next-fidelity-transforms-pr36 branch from e3d45dd to 55752cc Compare May 16, 2026 14:12
…ure-cleanup

refactor: organize Next fidelity adapter architecture
…nsform

fix: generalize CJS browser boundaries
@kasperpeulen kasperpeulen changed the title feat!: improve Next.js App Router fidelity chore: checkpoint failed Next Edge delegation experiment May 17, 2026
@kasperpeulen kasperpeulen changed the title chore: checkpoint failed Next Edge delegation experiment feat!: improve Next.js App Router fidelity May 18, 2026
@kasperpeulen kasperpeulen force-pushed the codex/next-fidelity-transforms-pr36 branch from e7f2535 to 0a19ebc Compare May 18, 2026 14:19
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.

1 participant