Update and shrink the `publish-ci` example set

Author: markeriksonCreated Aug 9, 2026Updated Aug 11, 2026
LabelsExample-Apps

(AI writeup result from my own research session)

Summary

The examples/publish-ci/ set has not had a real review since it was built in 2023. Recent commits on those directories are dependency bumps and chore: changes, not maintenance. The result is that we are spending CI time on toolchains that no longer represent the ecosystem, several examples test the same resolution path as each other, and two of our shipped build artifacts have no coverage at all.

This issue proposes cutting the set from 8 examples to 5, modernizing what remains, and fixing the CI configuration problems found along the way.

These examples are also consumed by React-Redux, Redux core, and Reselect, so most changes need a coordinated rollout. See Coordinating across repos at the bottom.

Current state

Example Toolchain Current release Resolves RTK via
cra4 react-scripts 4, Webpack 4 dead since 2022 module field → legacy-esm.js
cra5 react-scripts 5.0.1, Webpack 5 last published Jan 2022 exports modulemodern.mjs
next Next 13.2, Pages Router Next 16.3.0 exports modulemodern.mjs
vite Vite 7 Vite 8.2.1 exports modulemodern.mjs
node-standard Node, CJS package requiredist/cjs/index.js
node-esm Node, "type": "module" same assertions as node-standard
react-native RN 0.79.2 RN 0.86.2 react-native condition or field
expo Expo SDK ~53 Expo SDK 57.0.11 same as react-native

All four web examples still declare @reduxjs/toolkit: ^2.0.0-rc.3, react-redux: ^9.0.0-rc.0, and msw: ^1.3.2. They pull release-candidate ranges from 2023.

The three examples/type-portability/ projects (bundler, nodenext-cjs, nodenext-esm) are in better shape and are not proposed for changes here. They were built deliberately to prevent TypeScript type-portability regressions and they still do that job.

Problems found

1. Three web examples test one resolution path. cra5, next, and vite all resolve to dist/redux-toolkit.modern.mjs. RTK lists module-sync and module ahead of browser and import in its exports map, and Webpack 5, Next, and Vite all offer a module condition, so all three match at the same key. They differ in bundler internals — Babel vs SWC vs esbuild — but not in which RTK build they consume.

2. node-standard and node-esm assert the same things. Their test files are byte-identical apart from the CJS filename. Both run node --no-experimental-require-module test-cjs.* && node test-esm.mjs, so both cover CJS-require and ESM-import in one process each. The "type": "module" difference does not change which files get resolved.

3. require(esm) is not tested at all. Both Node examples pass --no-experimental-require-module, which suppresses the module-sync condition. On Node 22.12+ and 23+, require('@reduxjs/toolkit') matches module-sync and resolves to redux-toolkit.modern.mjs, not dist/cjs/index.js. That is the single biggest untested path in the package today, and it is the one most likely to affect real users.

4. dist/redux-toolkit.browser.mjs is reached by nothing. It is built, published, and referenced by exports["."].browser and the unpkg field, but browser is listed after module-sync and module, so no bundler ever matches it. (Separately: that artifact is intended as a script-tag / import-map replacement for the old UMD build, so pointing a bundler condition at it is arguably wrong. Tracking that in #5359.)

5. cra4 is the only thing reaching RTK's module field, and it is currently broken. In the React-Redux signals prototype branch it fails with:

./node_modules/react-redux/dist/react-redux.legacy-esm.js
Cannot find module: 'alien-signals'. Make sure this package is installed.

[email protected] ships an exports map with no main and no module field. Webpack 4 has no package.json exports support — that landed in Webpack 5.0.0, per the release post — so it cannot resolve the package at all.

6. examples/type-portability/** is missing from the paths-filter in tests.yml. A PR that touches only those examples skips CI entirely. This is a straight bug, independent of everything else here.

7. About 400 lines of application code are copied into every web example. cra4, cra5, and vite are byte-identical across all 15 shared source files. next differs only because its folder is named src/app-core/ instead of src/app/; the five differing files differ solely in import paths. There is no framework-specific application code in any of them.

8. Smaller CI issues.

  • NODE_OPTIONS: --openssl-legacy-provider is set for every job in the matrix. Only cra4 needs it.
  • Reselect sets up JDK 17 only when matrix.example == 'react-native', so the expo job gets no JDK.
  • RTK runs the matrix on Node 24 only. React-Redux and Reselect run Node 22 only. No repo tests both.
  • build:examples exists as a root script but no workflow calls it.

Plan

Proposed lineup: 8 → 5

Example Action Why
next Keep. Bump to Next 16, switch to App Router. Covers the framework most likely to break on packaging changes. App Router exercises RSC, which the Pages Router example does not. Apply the useState store initializer while we're in there.
vite Keep. Bump to Vite 8. Covers Rollup-based resolution and is the default for most new apps.
node Keep, merged from node-standard + node-esm. Add a require(esm) test mode. Same coverage as the two current examples in half the jobs, plus the largest current gap.
react-native Keep. Bump to RN 0.86. Only thing covering the react-native export condition.
expo Keep. Bump to SDK 57. Metro's exports handling differs from bare RN's. Worth keeping both.
cra5 Delete. Zero unique coverage. react-scripts has not shipped since Jan 2022.
cra4 Delete. See below.
esbuild Optional, new. The only proposal that would cover an artifact nothing else touches. Blocked on resolving the browser condition question in #5359 first — otherwise it would land on the minified production build and lock that in as expected behavior.

Across all four repos this takes the number of test-published-artifact jobs from 32 down to 20, or 24 with esbuild.

On deleting cra4 and legacy-esm

Deleting cra4 means nothing exercises RTK's module field or dist/redux-toolkit.legacy-esm.js anymore. That is an acceptable trade:

  • The module setup works today, and package structure is frozen for the rest of 2.x. A regression test protects against change, and there is no change coming to protect against.
  • legacy-esm keeps shipping in 2.x. Removing it mid-major would break anyone still on a pre-exports bundler.
  • legacy-esm and the module field get deleted together in RTK 3.

One thing would reopen this: if RTK itself takes on an exports-only dependency the way React-Redux did with alien-signals. At that point Webpack 4 is broken regardless of what the module field says, and shipping legacy-esm becomes a false promise.

Also in scope

  • Add examples/type-portability/** to the paths-filter in tests.yml.
  • Add a Node 22 entry alongside Node 24 in test-published-artifact.
  • Drop NODE_OPTIONS: --openssl-legacy-provider once cra4 is gone.
  • Shrink the duplicated example app. It exists to give the bundler something to resolve — it does not need a counter feature, a posts feature, MSW, and CSS modules to prove that all four entry points resolve. Roughly 60 lines covers the same paths. Note that a shared workspace package is not an option: each example has its own lockfile and is deliberately excluded from the root workspaces array, so that it resolves RTK from a registry tarball the way a real user does. Adding a workspace: link would change the thing under test and would break the downstream repos, which run yarn inside a single example directory with no root install.
  • Rename src/app/src/app-core/ in the remaining web examples, so next stops diverging. Next's App Router owns src/app/, so app-core is the correct name after the App Router switch anyway.

Not in scope

  • The 19 apps under examples/query/react/ and examples/action-listener/. They are much staler and 16 of them are embedded in the docs as CodeSandboxes. Separate problem, separate issue.
  • Rspack and React Router examples. Rspack is a Webpack-compatible resolver and would duplicate what next and vite cover. React Router apps are Vite-based, which vite already covers.
  • Anything requiring RTK 3: deleting legacy-esm, deleting the CJS build, dropping nodenext-cjs, raising the minimum Node version.

Coordinating across repos

Three other repos clone this repo at a pinned SHA and run the same example matrix. Any change to the matrix requires an edit in each of them.

Repo File Matrix location RTK pin Node
reduxjs/redux-toolkit .github/workflows/tests.yml ~line 219 n/a 24.x
reduxjs/react-redux .github/workflows/test.yml lines 155–165 576a02f8 (v2.12.0, 2026-05-15) 22.x
reduxjs/redux .github/workflows/test.yaml lines 201–212 7c49510f (2026-06-02) 24.x
reduxjs/reselect .github/workflows/build-and-test-types.yml lines 140–149 576a02f8 22.x

Because the downstream repos pin a SHA, deleting an example from RTK's master does not break them immediately. It breaks the next time someone bumps the pin. So the safe order is:

  1. Land the example changes in RTK.
  2. Update the matrix in each downstream repo.
  3. Bump the pin in each downstream repo.

Doing 3 before 2 breaks all three downstream CIs.

Medium-term fix worth considering: publish the example matrix as a single JSON file in this repo and have the downstream workflows read it after cloning. The list then lives in one place and the four-file edit goes away permanently.