Skip to content

feat!: support react 19 and drop support for 17#605

Merged
benlife5 merged 8 commits intomainfrom
react-19
Feb 27, 2026
Merged

feat!: support react 19 and drop support for 17#605
benlife5 merged 8 commits intomainfrom
react-19

Conversation

@benlife5
Copy link
Contributor

@benlife5 benlife5 commented Feb 26, 2026

  • Removed support for React 17
  • Added support for React 19
  • Added a local and GH check to test all supported version of react and node
  • Combined the unit_test step into the build step so that we only have to install/build the Node/React matrix once

Testing

  1. Ran new local testing matrix
  2. Temporarily added React 18-only code and verified that the testing matrix failed
  3. Installed in a basic Turtlehead Tacos starter and confirmed npm run dev and prod worked

@benlife5 benlife5 requested a review from a team as a code owner February 26, 2026 17:03
@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

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

Walkthrough

Removes React 17-specific client renderers and version-gating logic, standardizing on the modern client renderer (_client.tsx). Replaces ReactDOM.render with createRoot in the Vite plugin. Updates packages/pages peer dependencies and the playground to include React 19, removes a custom module loader and its registration, and simplifies template/module filepath resolution to a single client path. CI/workflow changes add a react-major matrix (18/19), adjust artifact naming and workflow steps, remove the reusable unit_test workflow invocation from the Pages pipeline, add a local matrix test script (scripts/test-matrix-local.sh), and update build/test artifact naming to include React version where applicable.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Script as scripts/test-matrix-local.sh
participant NVM as nvm / Node
participant PNPM as pnpm
participant Repo as repository (packages & playground)
participant Results as MatrixResults

Script->>NVM: load nvm & record initial Node
Script->>Repo: backup package.json & pnpm-lock.yaml
loop for each Node version
Script->>NVM: install & use Node version
Script->>PNPM: pnpm i
alt install failed
Script->>Results: record FAIL (install)
continue
end
loop for each React major (18,19)
Script->>PNPM: pnpm add react@ react-dom@ @types/react@ @types/react-dom@
PNPM-->>Repo: update deps
Script->>Repo: run build:js, build:types, playground build-test-site
alt build/test failed
Script->>Results: record FAIL (build/test)
else
Script->>Results: record PASS
end
end
end
Script->>Repo: restore backups & original Node
Script->>PNPM: pnpm i (restore deps)
Script->>Results: print summary
Results-->>Script: exit status

Possibly related PRs

Suggested reviewers

  • asanehisa
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: dropping React 17 support and adding React 19 support, which aligns with the primary objectives of the changeset.
Description check ✅ Passed The description is directly related to the changeset, detailing the removal of React 17 support, addition of React 19 support, and testing improvements across multiple files and workflows.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch react-19

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/pages/src/common/src/template/internal/getTemplateFilepaths.test.ts (1)

51-67: ⚠️ Potential issue | 🟡 Minor

Make the “default client template” test deterministic by mocking existsSync to false.

Right now this case depends on actual filesystem state; if src/templates/_client.tsx exists, the assertion can flip.

✅ Proposed fix
   it("uses default client template when no custom client", async () => {
     const rootPath = path.join("src", "templates");
     const util = await import("./util.js");
     vi.spyOn(util, "globSync").mockImplementation(() => [
       path.join(rootPath, "_client.tsx"),
       path.join(rootPath, "_server.tsx"),
       path.join(rootPath, "custom.tsx"),
     ]);
+    vi.spyOn(util, "existsSync").mockImplementation(() => false);

     const clientServerRenderTemplates = getGlobalClientServerRenderTemplates([
       new Path(path.join(process.cwd(), rootPath)),
     ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/pages/src/common/src/template/internal/getTemplateFilepaths.test.ts`
around lines 51 - 67, The test for default client template is nondeterministic
because it relies on the real filesystem; update the test to mock
util.existsSync to always return false so the code path chooses the default
instead of a real file. In the test where util.globSync is mocked and
getGlobalClientServerRenderTemplates is called, add vi.spyOn(util,
"existsSync").mockImplementation(() => false) before invoking
getGlobalClientServerRenderTemplates (and restore the spy after the test if
necessary) to ensure clientRenderTemplatePath resolves to the default in a
deterministic way.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/pages/src/vite-plugin/modules/plugin.ts`:
- Around line 148-149: The code calls
createRoot(moduleContainerForBuildUseOnly).render(...) but the existing guard
only checks for a module-level import of 'react-dom/client' (the conditional
around line 171) and not whether the createRoot symbol is actually imported;
update the import-injection logic so that when a file imports from
'react-dom/client' you also inspect its specifiers and if createRoot is not
among them, inject an explicit import { createRoot } from 'react-dom/client' (or
otherwise ensure a locally-declared createRoot binding exists) before emitting
the createRoot(...) call—make this change around the same logic that currently
detects 'react-dom/client' imports so createRoot is guaranteed to be defined at
the createRoot(moduleContainerForBuildUseOnly).render(...) site.

In `@scripts/test-matrix-local.sh`:
- Around line 99-101: The matrix loop in scripts/test-matrix-local.sh only
updates type packages (the pnpm --filter `@yext/pages` up
"@types/react@${REACT_TYPES_VERSION}"
"@types/react-dom@${REACT_DOM_TYPES_VERSION}") but not the actual React runtime,
causing builds to always use a single installed React version; update the loop
to also bump the runtime packages by including "react@${REACT_VERSION}" and
"react-dom@${REACT_DOM_VERSION}" (or align the existing REACT_TYPES_VERSION vars
to runtime vars) so that the pnpm up command updates both types and runtime for
each matrix iteration (modify the pnpm --filter `@yext/pages` up invocation in
scripts/test-matrix-local.sh accordingly).

---

Outside diff comments:
In
`@packages/pages/src/common/src/template/internal/getTemplateFilepaths.test.ts`:
- Around line 51-67: The test for default client template is nondeterministic
because it relies on the real filesystem; update the test to mock
util.existsSync to always return false so the code path chooses the default
instead of a real file. In the test where util.globSync is mocked and
getGlobalClientServerRenderTemplates is called, add vi.spyOn(util,
"existsSync").mockImplementation(() => false) before invoking
getGlobalClientServerRenderTemplates (and restore the spy after the test if
necessary) to ensure clientRenderTemplatePath resolves to the default in a
deterministic way.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 119487f and 8889eed.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • .github/workflows/build.yml
  • .github/workflows/unit_test.yml
  • package.json
  • packages/pages/package.json
  • packages/pages/src/common/src/module/internal/_client17.tsx
  • packages/pages/src/common/src/module/internal/getModuleFilepaths.ts
  • packages/pages/src/common/src/template/internal/_client17.tsx
  • packages/pages/src/common/src/template/internal/getTemplateFilepaths.test.ts
  • packages/pages/src/common/src/template/internal/getTemplateFilepaths.ts
  • packages/pages/src/common/src/template/internal/util.ts
  • packages/pages/src/vite-plugin/modules/plugin.ts
  • playground/multibrand-site/package.json
  • scripts/test-matrix-local.sh
💤 Files with no reviewable changes (4)
  • packages/pages/src/common/src/template/internal/_client17.tsx
  • packages/pages/src/common/src/module/internal/_client17.tsx
  • .github/workflows/unit_test.yml
  • packages/pages/src/common/src/template/internal/util.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
.github/workflows/build.yml (1)

36-50: Consider quoting the version variables to prevent potential shell word splitting.

The version variables are unquoted in the pnpm up command. While these specific values are safe, quoting them is a defensive practice.

♻️ Suggested improvement
          pnpm --filter `@yext/pages` up \
-           `@types/react`@${REACT_TYPES_VERSION} \
-           `@types/react-dom`@${REACT_DOM_TYPES_VERSION}
+           "@types/react@${REACT_TYPES_VERSION}" \
+           "@types/react-dom@${REACT_DOM_TYPES_VERSION}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build.yml around lines 36 - 50, The shell command uses
unquoted variables REACT_TYPES_VERSION and REACT_DOM_TYPES_VERSION in the pnpm
--filter `@yext/pages` up invocation which can risk word-splitting; update the
command that calls pnpm (the pnpm --filter `@yext/pages` up line) to use the
quoted forms of those variables so the arguments are passed as single tokens
(quote the variable expansions for REACT_TYPES_VERSION and
REACT_DOM_TYPES_VERSION).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/build.yml:
- Around line 36-50: The shell command uses unquoted variables
REACT_TYPES_VERSION and REACT_DOM_TYPES_VERSION in the pnpm --filter `@yext/pages`
up invocation which can risk word-splitting; update the command that calls pnpm
(the pnpm --filter `@yext/pages` up line) to use the quoted forms of those
variables so the arguments are passed as single tokens (quote the variable
expansions for REACT_TYPES_VERSION and REACT_DOM_TYPES_VERSION).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8889eed and 6e688bc.

📒 Files selected for processing (4)
  • .github/workflows/build.yml
  • .github/workflows/build_and_test_pages.yml
  • THIRD-PARTY-NOTICES
  • package.json
💤 Files with no reviewable changes (1)
  • .github/workflows/build_and_test_pages.yml

@mkilpatrick
Copy link
Collaborator

Check if packages/pages/src/bin/loader.js is still necessary. I think so.

@benlife5
Copy link
Contributor Author

Seems ok without loader.js

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/build.yml:
- Around line 36-50: The workflow step only updates `@types` packages but not
runtime React, so modify the script that sets
REACT_TYPES_VERSION/REACT_DOM_TYPES_VERSION to also set REACT_VERSION and
REACT_DOM_VERSION based on matrix.react-major (e.g., when matrix.react-major ==
"18" set REACT_VERSION and REACT_DOM_VERSION to the appropriate React 18 semver,
otherwise set them for React 19), then include "@yext/pages" runtime updates in
the pnpm command by adding "react@${REACT_VERSION}" and
"react-dom@${REACT_DOM_VERSION}" to the pnpm --filter `@yext/pages` up invocation
so both runtime and type packages are bumped during the matrix runs.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6e688bc and ff6f1c0.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • .github/workflows/build.yml
  • .github/workflows/playwright.yml
  • packages/pages/package.json
  • packages/pages/src/bin/loader.js
  • packages/pages/src/bin/spawn.ts
  • packages/pages/src/vite-plugin/modules/plugin.ts
  • scripts/test-matrix-local.sh
💤 Files with no reviewable changes (2)
  • packages/pages/src/bin/spawn.ts
  • packages/pages/src/bin/loader.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/pages/package.json
  • scripts/test-matrix-local.sh
  • packages/pages/src/vite-plugin/modules/plugin.ts

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
.github/workflows/build.yml (1)

70-70: Consider consistent artifact naming for both React versions.

The current naming creates asymmetry: React 19 builds include -react-19 in the artifact name, but React 18 builds don't include a version indicator. This could cause confusion when viewing artifacts.

For improved clarity, consider always including the React version for pages builds:

♻️ Proposed fix for consistent naming
-          name: ${{ inputs.package == 'pages' && matrix.react-major == '19' && format('build-output-{0}-react-19', matrix.node-version) || format('build-output-{0}', matrix.node-version) }}
+          name: ${{ inputs.package == 'pages' && format('build-output-{0}-react-1}', matrix.node-version, matrix.react-major) || format('build-output-{0}', matrix.node-version) }}

This would produce build-output-20.x-react-18 and build-output-20.x-react-19 for pages, making both versions explicit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/build.yml at line 70, The artifact name expression
currently only appends "-react-19" when matrix.react-major == '19', causing
inconsistent names; update the name expression used for the job (the line with
name: ${{ ... format('build-output-{0}-react-19', matrix.node-version) ||
format('build-output-{0}', matrix.node-version) }}) so that pages builds always
include the React major version (e.g., format('build-output-{0}-react-{1}',
matrix.node-version, matrix.react-major) or equivalent), ensuring both React 18
and 19 artifacts are named like build-output-20.x-react-18 and
build-output-20.x-react-19.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In @.github/workflows/build.yml:
- Line 70: The artifact name expression currently only appends "-react-19" when
matrix.react-major == '19', causing inconsistent names; update the name
expression used for the job (the line with name: ${{ ...
format('build-output-{0}-react-19', matrix.node-version) ||
format('build-output-{0}', matrix.node-version) }}) so that pages builds always
include the React major version (e.g., format('build-output-{0}-react-{1}',
matrix.node-version, matrix.react-major) or equivalent), ensuring both React 18
and 19 artifacts are named like build-output-20.x-react-18 and
build-output-20.x-react-19.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff6f1c0 and c5c68ec.

📒 Files selected for processing (1)
  • .github/workflows/build.yml

@benlife5 benlife5 requested a review from mkilpatrick February 26, 2026 21:16
mkilpatrick
mkilpatrick previously approved these changes Feb 27, 2026
fi

echo "Running Playwright in ${playground_dir#$ROOT_DIR/}"
if ! PLAYWRIGHT_HTML_OPEN=never pnpm --dir "$playground_dir" exec playwright test --reporter=line; then
Copy link
Collaborator

Choose a reason for hiding this comment

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

And this also happens in the github action?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, .github/workflows/playwright.yml:40

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/test-matrix-local.sh`:
- Line 2: Change the shell options to fail-fast by updating the existing "set
-uo pipefail" invocation to include -e (i.e. "set -euo pipefail"), and harden
the unchecked backup/restore cp operations by adding explicit pre-checks before
any cp that restores package.json or lockfiles: verify the source backup file
exists and is non-empty (e.g., test -s /path/to/backup) and only then run cp,
otherwise exit with an error; locate the "set -uo pipefail" line and the cp
commands that copy package.json / lockfiles (the unchecked cp lines referenced
in the comment) and wrap those restore cp calls with the existence/size tests so
you never overwrite real files with missing/empty backups.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5c68ec and 5502a01.

📒 Files selected for processing (1)
  • scripts/test-matrix-local.sh

@benlife5 benlife5 merged commit e9131c4 into main Feb 27, 2026
31 checks passed
@benlife5 benlife5 deleted the react-19 branch February 27, 2026 20:25
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.

3 participants