Skip to content

fix(bundler): disable SSRStaticReplace in vite dev mode#739

Merged
harlan-zw merged 1 commit into
mainfrom
fix/ssr-static-replace-dev-mode
Apr 12, 2026
Merged

fix(bundler): disable SSRStaticReplace in vite dev mode#739
harlan-zw merged 1 commit into
mainfrom
fix/ssr-static-replace-dev-mode

Conversation

@harlan-zw

@harlan-zw harlan-zw commented Apr 11, 2026

Copy link
Copy Markdown
Collaborator

❓ Type of change

  • 📖 Documentation
  • 🐞 Bug fix
  • 👌 Enhancement
  • ✨ New feature
  • 🧹 Chore
  • ⚠️ Breaking change

📚 Description

In vite dev mode, SSR and client environments share the same dev server, but SSRStaticReplace was rewriting head.ssr to false for both, forcing useHead down the clientUseHead path during SSR renders. That broke reactive ref resolution in SSR because clientUseHead relies on Vue watchers rather than synchronous head.push.

Fix disables the static replacement in dev mode so the runtime head.ssr value is preserved and each environment behaves correctly. Unit tests cover client builds, SSR builds, and dev mode.

In vite dev mode (serve), both SSR and client environments share the
same vite dev server. The SSRStaticReplace plugin was replacing `head.ssr`
with `false` for all environments, causing useHead to always use
clientUseHead instead of head.push during SSR renders.

This broke reactive ref resolution in SSR because clientUseHead uses
Vue watchers (client behavior) instead of synchronous push (SSR behavior).

The fix disables the static replacement in dev mode, preserving the
runtime `head.ssr` value so each environment behaves correctly.
@coderabbitai

coderabbitai Bot commented Apr 11, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Modified the SSRStaticReplace Vite plugin to add an enabled flag that disables static replacement during serve mode. The plugin now short-circuits transformation checks when disabled, ensuring head.ssr replacement only occurs during build passes, not development server execution. Added comprehensive test coverage validating behavior across client builds, SSR builds, and dev mode.

Changes

Cohort / File(s) Summary
SSRStaticReplace Plugin Update
packages/bundler/src/unplugin/SSRStaticReplace.ts
Added enabled flag initialized to true; transformInclude() short-circuits returning false when disabled. Vite integration's apply hook now disables the plugin during serve mode (env.command === 'serve') to prevent transformation in development.
Plugin Testing
packages/bundler/test/ssrStaticReplace.test.ts
New test file with helper functions to instantiate and configure the plugin. Tests validate that transformInclude correctly identifies target modules, client builds replace head.ssr with false, SSR builds replace with true, and dev mode skips transformation entirely.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A flag to pause the static dance,
In dev mode, let the code stand a chance!
Build time swaps with care so fine,
Client says false, SSR says true—divine,
Tests confirm each branch does right! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: disabling SSRStaticReplace in Vite dev mode to fix SSR behavior.
Description check ✅ Passed The PR description includes the required Type of Change selection (Bug fix) and a detailed Description explaining the problem, solution, and testing approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/ssr-static-replace-dev-mode

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 and usage tips.

@github-actions

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Bundle Size Gzipped
Client (Minimal) 10.7 kB 4.4 kB
Server (Minimal) 10.6 kB 4.3 kB
Vue Client (Minimal) 11.8 kB 4.8 kB
Vue Server (Minimal) 11.6 kB 4.7 kB

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
packages/bundler/src/unplugin/SSRStaticReplace.ts (1)

47-58: Make apply() state assignment idempotent.

At Line 52 and Line 56, enabled/ssr are only flipped in one direction. If apply() is invoked again on the same plugin instance with a different env, stale state can leak. Set both flags from env every call.

♻️ Proposed refactor
      apply(_config: UserConfig, env: ConfigEnv): boolean {
-        // In dev mode (serve), both SSR and client environments share the same
-        // vite dev server. Statically replacing head.ssr would force one value
-        // for both environments, breaking SSR renders. Only apply during builds
-        // where SSR and client are separate passes.
-        if (env.command === 'serve') {
-          enabled = false
-          return true
-        }
-        if (env.isSsrBuild)
-          ssr = true
+        // Keep plugin state derived from current env on every apply call.
+        enabled = env.command !== 'serve'
+        ssr = !!env.isSsrBuild
         return true
       },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/bundler/src/unplugin/SSRStaticReplace.ts` around lines 47 - 58, The
apply() method mutates plugin-level flags enabled and ssr only in one direction
causing stale state when apply() is called multiple times; make assignments
idempotent by explicitly setting enabled = (env.command !== 'serve') and ssr =
!!env.isSsrBuild inside apply() so both flags reflect the current env on every
invocation (update the apply function where enabled and ssr are currently
toggled).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/bundler/src/unplugin/SSRStaticReplace.ts`:
- Around line 47-58: The apply() method mutates plugin-level flags enabled and
ssr only in one direction causing stale state when apply() is called multiple
times; make assignments idempotent by explicitly setting enabled = (env.command
!== 'serve') and ssr = !!env.isSsrBuild inside apply() so both flags reflect the
current env on every invocation (update the apply function where enabled and ssr
are currently toggled).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ee4914de-eaeb-4a40-8f6e-e1263be42078

📥 Commits

Reviewing files that changed from the base of the PR and between 2d097ca and 7cf01f2.

📒 Files selected for processing (2)
  • packages/bundler/src/unplugin/SSRStaticReplace.ts
  • packages/bundler/test/ssrStaticReplace.test.ts

@harlan-zw harlan-zw merged commit 13121c6 into main Apr 12, 2026
8 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.

1 participant