;
+};
+```
+
+### Step 4: Update React on Rails Registration
+
+No changes needed for component registration. Your existing code should work:
+
+```javascript
+import ReactOnRails from 'react-on-rails';
+import MyComponent from './MyComponent';
+
+ReactOnRails.register({ MyComponent });
+```
+
+### Step 5: Rebuild Your Assets
+
+```bash
+# Clear any cached builds
+rm -rf public/packs
+
+# Rebuild
+bin/rails assets:precompile
+# OR if using Shakapacker in development
+bin/shakapacker
+```
+
+## Common Issues & Solutions
+
+### Issue: "export 'createContext' was not found in 'react'"
+
+**Cause**: TypeScript is generating invalid imports due to `esModuleInterop: false`.
+
+**Solution**:
+
+1. Enable `esModuleInterop: true` in `tsconfig.json`, OR
+2. Use named imports everywhere:
+ ```typescript
+ import { createContext, useContext } from 'react';
+ ```
+
+### Issue: "export 'Component' was not found in 'react'"
+
+**Cause**: Same as above - TypeScript compilation issue.
+
+**Solution**: Change from:
+
+```typescript
+import * as React from 'react';
+class MyComponent extends React.Component {}
+```
+
+To:
+
+```typescript
+import { Component } from 'react';
+class MyComponent extends Component {}
+```
+
+### Issue: Server bundle fails with React import errors
+
+**Cause**: The server webpack bundle is incorrectly resolving to the `react-server` condition.
+
+**Solution**: Ensure your server webpack config doesn't include `'react-server'` in `resolve.conditionNames` unless you're building an RSC bundle. React on Rails handles this automatically.
+
+### Issue: Third-party libraries fail with React import errors
+
+**Cause**: Libraries in `node_modules` may have dependencies that use namespace imports.
+
+**Solutions**:
+
+1. Update the library to a React 19-compatible version
+2. Add the library to webpack externals if it's server-only
+3. Use webpack's `resolve.alias` to ensure React resolves correctly
+
+## TypeScript Configuration
+
+### Recommended Configuration for React 19
+
+```json
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "jsx": "react-jsx",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+
+ // RECOMMENDED: Enable for easier React imports
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+
+ // Type checking
+ "strict": true,
+ "skipLibCheck": true,
+
+ // React 19 specific
+ "types": ["react", "react-dom"]
+ }
+}
+```
+
+### If You Must Keep esModuleInterop: false
+
+```json
+{
+ "compilerOptions": {
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": false,
+
+ // Use these settings
+ "jsx": "react-jsx",
+ "module": "ESNext",
+ "moduleResolution": "bundler"
+ }
+}
+```
+
+**Important**: With `esModuleInterop: false`, you MUST use named imports:
+
+```typescript
+// ✅ CORRECT
+import { useState, useEffect, type FC } from 'react';
+
+// ❌ WRONG - Will generate invalid code
+import * as React from 'react';
+React.useState(); // This will fail to compile correctly
+```
+
+## React Server Components (Pro)
+
+If you're using React on Rails Pro with React Server Components:
+
+### Update Pro Package
+
+```bash
+# Ensure you're on React on Rails Pro 16.1.1+
+bundle update react_on_rails_pro
+```
+
+### RSC-Specific Considerations
+
+1. **Named Imports Required**: RSC components MUST use named imports:
+
+```typescript
+// ✅ CORRECT for RSC
+import { createContext, useContext } from 'react';
+
+// ❌ WRONG - Breaks with React 19
+import * as React from 'react';
+```
+
+2. **'use client' Directive**: Client components still need the directive:
+
+```typescript
+'use client';
+
+import { useState } from 'react';
+
+export default function ClientComponent() {
+ const [count, setCount] = useState(0);
+ return ;
+}
+```
+
+3. **Server Components**: No 'use client' directive needed:
+
+```typescript
+// No directive - this is a server component
+export default async function ServerComponent() {
+ const data = await fetchData();
+ return
{data}
;
+}
+```
+
+### RSC Bundle Configuration
+
+React on Rails Pro automatically configures the RSC bundle to use the correct React build. No manual configuration needed.
+
+## Testing Your Upgrade
+
+### 1. Run Your Test Suite
+
+```bash
+# Ruby tests
+bundle exec rspec
+
+# JavaScript tests
+yarn test
+```
+
+### 2. Build in Development
+
+```bash
+# Start your dev server
+bin/dev
+
+# Or manually build
+bin/shakapacker
+```
+
+Check the console for:
+
+- ❌ No webpack errors about React imports
+- ❌ No "export not found" errors
+- ✅ Clean build with no warnings
+
+### 3. Test Server-Side Rendering
+
+If you use SSR, test a server-rendered page:
+
+```bash
+# Start your Rails server
+bin/rails s
+
+# Visit a page with server-rendered React
+curl http://localhost:3000/your-page | grep "data-react"
+```
+
+### 4. Test in Production Mode
+
+```bash
+RAILS_ENV=production NODE_ENV=production bin/rails assets:precompile
+```
+
+Ensure no errors during compilation.
+
+## Rollback Plan
+
+If you encounter issues and need to rollback:
+
+```bash
+# Downgrade React
+yarn add react@18.3.1 react-dom@18.3.1
+
+# Downgrade types (if using TypeScript)
+yarn add -D @types/react@18 @types/react-dom@18
+
+# Rebuild
+rm -rf public/packs
+bin/rails assets:precompile
+```
+
+## Additional Resources
+
+- [Official React 19 Upgrade Guide](https://react.dev/blog/2024/04/25/react-19-upgrade-guide)
+- [React 19 Release Notes](https://react.dev/blog/2024/12/05/react-19)
+- [Shakapacker Documentation](https://github.com/shakacode/shakapacker)
+- [React on Rails Documentation](https://www.shakacode.com/react-on-rails/docs/)
+
+## Getting Help
+
+If you encounter issues during the upgrade:
+
+1. **Check the Issues**: [React on Rails GitHub Issues](https://github.com/shakacode/react_on_rails/issues)
+2. **Community Forum**: [ShakaCode Forum](https://forum.shakacode.com)
+3. **Discord**: [React on Rails Discord](https://discord.gg/reactonrails)
+4. **Commercial Support**: Contact [ShakaCode](mailto:justin@shakacode.com) for professional support
+
+## Changelog
+
+- **2025-11-06**: Initial React 19 upgrade guide created
+- Added TypeScript configuration guidelines
+- Added React Server Components section
+- Added common troubleshooting scenarios
diff --git a/docs/upgrading/upgrading-react-on-rails.md b/docs/upgrading/upgrading-react-on-rails.md
index 5a04d10a41..1d69f9943a 100644
--- a/docs/upgrading/upgrading-react-on-rails.md
+++ b/docs/upgrading/upgrading-react-on-rails.md
@@ -21,6 +21,16 @@ rails generate react_on_rails:install
- `shakapacker.yml` settings
- other configuration files
+## Upgrading to React 19
+
+If you're upgrading to React 19, please see the comprehensive [React 19 Upgrade Guide](./react-19-upgrade-guide.md).
+
+**Key Changes in React 19:**
+
+- Conditional package exports affect module resolution
+- TypeScript import patterns may need updates
+- React Server Components require named imports
+
## Upgrading to v16
### Breaking Changes
diff --git a/eslint-rules/README.md b/eslint-rules/README.md
new file mode 100644
index 0000000000..ed543449e1
--- /dev/null
+++ b/eslint-rules/README.md
@@ -0,0 +1,110 @@
+# Custom ESLint Rules
+
+This directory contains custom ESLint rules specific to React on Rails.
+
+## Rules
+
+### `no-use-client-in-server-files`
+
+Prevents the `'use client'` directive from being used in `.server.tsx` and `.server.ts` files.
+
+#### Why This Rule Exists
+
+Files ending with `.server.tsx` are intended for server-side rendering in React Server Components (RSC) architecture. The `'use client'` directive forces webpack to bundle these files as client components, which creates a fundamental contradiction and causes errors when using React's `react-server` conditional exports.
+
+This issue became apparent with Shakapacker 9.3.0+, which properly honors `resolve.conditionNames` in webpack configurations. When webpack resolves imports with the `react-server` condition, React's server exports intentionally omit client-only APIs like:
+
+- `createContext`, `useContext`
+- `useState`, `useEffect`, `useLayoutEffect`, `useReducer`
+- `Component`, `PureComponent`
+- Other hooks (`use*` functions)
+
+#### Examples
+
+❌ **Incorrect** - Will trigger an error:
+
+```typescript
+// Component.server.tsx
+'use client';
+
+import React from 'react';
+
+export function MyComponent() {
+ return
Component
;
+}
+```
+
+✅ **Correct** - No directive in server files:
+
+```typescript
+// Component.server.tsx
+import React from 'react';
+
+export function MyComponent() {
+ return
Component
;
+}
+```
+
+✅ **Correct** - Use `'use client'` in client files:
+
+```typescript
+// Component.client.tsx or Component.tsx
+'use client';
+
+import React, { useState } from 'react';
+
+export function MyComponent() {
+ const [count, setCount] = useState(0);
+ return
Count: {count}
;
+}
+```
+
+#### Auto-fix
+
+This rule includes an automatic fixer that will remove the `'use client'` directive from `.server.tsx` files when you run ESLint with the `--fix` option:
+
+```bash
+npx eslint --fix path/to/file.server.tsx
+```
+
+#### Related
+
+- **Issue:** [Shakapacker #805 - Breaking change in 9.3.0](https://github.com/shakacode/shakapacker/issues/805)
+- **Fix PR:** [React on Rails #1896](https://github.com/shakacode/react_on_rails/pull/1896)
+- **Commit:** [86979dca - Remove 'use client' from .server.tsx files](https://github.com/shakacode/react_on_rails/commit/86979dca)
+
+#### Configuration
+
+This rule is automatically enabled in the React on Rails ESLint configuration at the `error` level. It's defined in `eslint.config.ts`:
+
+```typescript
+plugins: {
+ 'react-on-rails': {
+ rules: {
+ 'no-use-client-in-server-files': noUseClientInServerFiles,
+ },
+ },
+},
+
+rules: {
+ 'react-on-rails/no-use-client-in-server-files': 'error',
+ // ... other rules
+}
+```
+
+## Testing
+
+To run tests for the custom rules:
+
+```bash
+node eslint-rules/no-use-client-in-server-files.test.cjs
+```
+
+## Adding New Custom Rules
+
+To add a new custom ESLint rule:
+
+1. Create the rule file in this directory (use `.cjs` extension for CommonJS)
+2. Create a corresponding test file (e.g., `rule-name.test.cjs`)
+3. Import and register the rule in `eslint.config.ts`
+4. Add documentation to this README
diff --git a/eslint.config.ts b/eslint.config.ts
index 20a11aaa9b..618016ee33 100644
--- a/eslint.config.ts
+++ b/eslint.config.ts
@@ -85,7 +85,21 @@ const config = tsEslint.config([
},
},
+ plugins: {
+ 'react-on-rails': {
+ rules: {
+ 'no-use-client-in-server-files': noUseClientInServerFiles,
+ },
+ },
+ },
+
rules: {
+ // Custom React on Rails rules
+ 'react-on-rails/no-use-client-in-server-files': 'error',
+
+ // React 17+ with new JSX transform doesn't require React in scope
+ 'react/react-in-jsx-scope': 'off',
+
'no-shadow': 'off',
'no-console': 'off',
'function-paren-newline': 'off',
@@ -160,13 +174,6 @@ const config = tsEslint.config([
},
{
files: ['**/*.server.ts', '**/*.server.tsx'],
- plugins: {
- 'react-on-rails': {
- rules: {
- 'no-use-client-in-server-files': noUseClientInServerFiles,
- },
- },
- },
rules: {
'react-on-rails/no-use-client-in-server-files': 'error',
},
diff --git a/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt b/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt
index ec6e527918..f4d0e8734f 100644
--- a/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt
+++ b/lib/generators/react_on_rails/templates/base/base/config/webpack/serverWebpackConfig.js.tt
@@ -50,6 +50,8 @@ const configureServer = () => {
serverWebpackConfig.output = {
filename: 'server-bundle.js',
globalObject: 'this',
+ // Note: libraryTarget should only be 'commonjs2' when using Node renderer
+ // For ExecJS (default), leave it undefined to allow proper evaluation
// If using the React on Rails Pro node server renderer, uncomment the next line
// libraryTarget: 'commonjs2',
path: require('path').resolve(__dirname, '../../ssr-generated'),
@@ -111,8 +113,18 @@ const configureServer = () => {
// If using the default 'web', then libraries like Emotion and loadable-components
// break with SSR. The fix is to use a node renderer and change the target.
- // If using the React on Rails Pro node server renderer, uncomment the next line
- // serverWebpackConfig.target = 'node'
+ // React 19 requires target: 'node' to properly handle Node.js built-in modules
+ serverWebpackConfig.target = 'node';
+
+ // React 19 Fix: Prevent webpack from resolving to react-server condition
+ // The server-bundle needs the full React with hooks for SSR, not the react-server build
+ // Explicitly set conditionNames without react-server
+ if (!serverWebpackConfig.resolve) {
+ serverWebpackConfig.resolve = {};
+ }
+ // For target: 'node', webpack defaults to ['node', 'import', 'require', 'default']
+ // We explicitly list them to ensure react-server is not included
+ serverWebpackConfig.resolve.conditionNames = ['node', 'import', 'require', 'default'];
return serverWebpackConfig;
};
diff --git a/packages/react-on-rails-pro/src/RSCProvider.tsx b/packages/react-on-rails-pro/src/RSCProvider.tsx
index 3a25161d0e..11689d5012 100644
--- a/packages/react-on-rails-pro/src/RSCProvider.tsx
+++ b/packages/react-on-rails-pro/src/RSCProvider.tsx
@@ -12,17 +12,19 @@
* https://github.com/shakacode/react_on_rails/blob/master/REACT-ON-RAILS-PRO-LICENSE.md
*/
-import * as React from 'react';
+'use client';
+
+import { createContext, useContext, type ReactNode } from 'react';
import type { ClientGetReactServerComponentProps } from './getReactServerComponent.client.ts';
import { createRSCPayloadKey } from './utils.ts';
type RSCContextType = {
- getComponent: (componentName: string, componentProps: unknown) => Promise;
+ getComponent: (componentName: string, componentProps: unknown) => Promise;
- refetchComponent: (componentName: string, componentProps: unknown) => Promise;
+ refetchComponent: (componentName: string, componentProps: unknown) => Promise;
};
-const RSCContext = React.createContext(undefined);
+const RSCContext = createContext(undefined);
/**
* Creates a provider context for React Server Components.
@@ -46,9 +48,9 @@ const RSCContext = React.createContext(undefined);
export const createRSCProvider = ({
getServerComponent,
}: {
- getServerComponent: (props: ClientGetReactServerComponentProps) => Promise;
+ getServerComponent: (props: ClientGetReactServerComponentProps) => Promise;
}) => {
- const fetchRSCPromises: Record> = {};
+ const fetchRSCPromises: Record> = {};
const getComponent = (componentName: string, componentProps: unknown) => {
const key = createRSCPayloadKey(componentName, componentProps);
@@ -74,7 +76,7 @@ export const createRSCProvider = ({
const contextValue = { getComponent, refetchComponent };
- return ({ children }: { children: React.ReactNode }) => {
+ return ({ children }: { children: ReactNode }) => {
return {children};
};
};
@@ -99,7 +101,7 @@ export const createRSCProvider = ({
* ```
*/
export const useRSC = () => {
- const context = React.useContext(RSCContext);
+ const context = useContext(RSCContext);
if (!context) {
throw new Error('useRSC must be used within a RSCProvider');
}
diff --git a/packages/react-on-rails-pro/src/RSCRoute.tsx b/packages/react-on-rails-pro/src/RSCRoute.tsx
index 84d2a4b34c..7a8a042545 100644
--- a/packages/react-on-rails-pro/src/RSCRoute.tsx
+++ b/packages/react-on-rails-pro/src/RSCRoute.tsx
@@ -16,7 +16,7 @@
'use client';
-import * as React from 'react';
+import { Component, use, type ReactNode } from 'react';
import { useRSC } from './RSCProvider.tsx';
import { ServerComponentFetchError } from './ServerComponentFetchError.ts';
@@ -24,11 +24,11 @@ import { ServerComponentFetchError } from './ServerComponentFetchError.ts';
* Error boundary component for RSCRoute that adds server component name and props to the error
* So, the parent ErrorBoundary can refetch the server component
*/
-class RSCRouteErrorBoundary extends React.Component<
- { children: React.ReactNode; componentName: string; componentProps: unknown },
+class RSCRouteErrorBoundary extends Component<
+ { children: ReactNode; componentName: string; componentProps: unknown },
{ error: Error | null }
> {
- constructor(props: { children: React.ReactNode; componentName: string; componentProps: unknown }) {
+ constructor(props: { children: ReactNode; componentName: string; componentProps: unknown }) {
super(props);
this.state = { error: null };
}
@@ -75,9 +75,9 @@ export type RSCRouteProps = {
componentProps: unknown;
};
-const PromiseWrapper = ({ promise }: { promise: Promise }) => {
- // React.use is available in React 18.3+
- const promiseResult = React.use(promise);
+const PromiseWrapper = ({ promise }: { promise: Promise }) => {
+ // use is available in React 18.3+
+ const promiseResult = use(promise);
// In case that an error happened during the rendering of the RSC payload before the rendering of the component itself starts
// RSC bundle will return an error object serialized inside the RSC payload
@@ -88,7 +88,7 @@ const PromiseWrapper = ({ promise }: { promise: Promise }) => {
return promiseResult;
};
-const RSCRoute = ({ componentName, componentProps }: RSCRouteProps): React.ReactNode => {
+const RSCRoute = ({ componentName, componentProps }: RSCRouteProps): ReactNode => {
const { getComponent } = useRSC();
const componentPromise = getComponent(componentName, componentProps);
return (
diff --git a/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.server.tsx b/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.server.tsx
index 43a81025e5..71f0f0448e 100644
--- a/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.server.tsx
+++ b/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/AsyncOnServerSyncOnClient.server.tsx
@@ -1,5 +1,3 @@
-'use client';
-
import wrapServerComponentRenderer from 'react-on-rails-pro/wrapServerComponentRenderer/server';
import AsyncOnServerSyncOnClient from '../components/AsyncOnServerSyncOnClient';
diff --git a/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.server.tsx b/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.server.tsx
index 22c3e3d2eb..23d92a1222 100644
--- a/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.server.tsx
+++ b/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/LazyApolloGraphQLApp.server.tsx
@@ -1,5 +1,3 @@
-'use client';
-
import React from 'react';
import { renderToString } from 'react-dom/server';
import { getMarkupFromTree } from '@apollo/client/react/ssr';
diff --git a/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx b/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx
index bb32707ecd..9ff1886628 100644
--- a/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx
+++ b/react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/ServerComponentRouter.server.tsx
@@ -1,5 +1,3 @@
-'use client';
-
import * as React from 'react';
import { StaticRouter } from 'react-router-dom/server.js';
import { RailsContext, ReactComponentOrRenderFunction } from 'react-on-rails-pro';
diff --git a/react_on_rails_pro/spec/dummy/config/webpack/serverWebpackConfig.js b/react_on_rails_pro/spec/dummy/config/webpack/serverWebpackConfig.js
index f47ea2ba26..4111738640 100644
--- a/react_on_rails_pro/spec/dummy/config/webpack/serverWebpackConfig.js
+++ b/react_on_rails_pro/spec/dummy/config/webpack/serverWebpackConfig.js
@@ -123,6 +123,16 @@ const configureServer = (rscBundle = false) => {
serverWebpackConfig.node = false;
+ // React 19 Fix: Prevent webpack from resolving to react-server condition
+ // The server-bundle needs the full React with hooks for SSR, not the react-server build
+ // Explicitly set conditionNames without react-server
+ if (!serverWebpackConfig.resolve) {
+ serverWebpackConfig.resolve = {};
+ }
+ // For target: 'node', webpack defaults to ['node', 'import', 'require', 'default']
+ // We explicitly list them to ensure react-server is not included
+ serverWebpackConfig.resolve.conditionNames = ['node', 'import', 'require', 'default'];
+
return serverWebpackConfig;
};
diff --git a/react_on_rails_pro/spec/dummy/package.json b/react_on_rails_pro/spec/dummy/package.json
index a85f8492b0..a649cd0c8b 100644
--- a/react_on_rails_pro/spec/dummy/package.json
+++ b/react_on_rails_pro/spec/dummy/package.json
@@ -79,6 +79,7 @@
"@babel/preset-typescript": "^7.23.2",
"@playwright/test": "^1.56.1",
"@pmmmwh/react-refresh-webpack-plugin": "0.5.3",
+ "@swc/core": "^1.15.0",
"@tailwindcss/aspect-ratio": "^0.4.2",
"@tailwindcss/forms": "^0.5.3",
"@tailwindcss/typography": "^0.5.9",
@@ -88,6 +89,7 @@
"jsdom": "^16.4.0",
"pino-pretty": "^13.0.0",
"preload-webpack-plugin": "^3.0.0-alpha.1",
+ "swc-loader": "^0.2.6",
"typescript": "^5.2.2",
"webpack-dev-server": "^4.7.3"
},
diff --git a/react_on_rails_pro/spec/execjs-compatible-dummy/config/webpack/serverWebpackConfig.js b/react_on_rails_pro/spec/execjs-compatible-dummy/config/webpack/serverWebpackConfig.js
index 9673f64a0a..749ed8fb23 100644
--- a/react_on_rails_pro/spec/execjs-compatible-dummy/config/webpack/serverWebpackConfig.js
+++ b/react_on_rails_pro/spec/execjs-compatible-dummy/config/webpack/serverWebpackConfig.js
@@ -48,6 +48,8 @@ const configureServer = () => {
serverWebpackConfig.output = {
filename: 'server-bundle.js',
globalObject: 'this',
+ // Note: libraryTarget should only be 'commonjs2' when using Node renderer
+ // For ExecJS (default), leave it undefined to allow proper evaluation
// If using the React on Rails Pro node server renderer, uncomment the next line
// libraryTarget: 'commonjs2',
path: config.outputPath,
@@ -109,8 +111,18 @@ const configureServer = () => {
// If using the default 'web', then libraries like Emotion and loadable-components
// break with SSR. The fix is to use a node renderer and change the target.
- // If using the React on Rails Pro node server renderer, uncomment the next line
- // serverWebpackConfig.target = 'node'
+ // React 19 requires target: 'node' to properly handle Node.js built-in modules
+ serverWebpackConfig.target = 'node';
+
+ // React 19 Fix: Prevent webpack from resolving to react-server condition
+ // The server-bundle needs the full React with hooks for SSR, not the react-server build
+ // Explicitly set conditionNames without react-server
+ if (!serverWebpackConfig.resolve) {
+ serverWebpackConfig.resolve = {};
+ }
+ // For target: 'node', webpack defaults to ['node', 'import', 'require', 'default']
+ // We explicitly list them to ensure react-server is not included
+ serverWebpackConfig.resolve.conditionNames = ['node', 'import', 'require', 'default'];
return serverWebpackConfig;
};
diff --git a/react_on_rails_pro/spec/execjs-compatible-dummy/package.json b/react_on_rails_pro/spec/execjs-compatible-dummy/package.json
index 2a73e44834..507b3d4c84 100644
--- a/react_on_rails_pro/spec/execjs-compatible-dummy/package.json
+++ b/react_on_rails_pro/spec/execjs-compatible-dummy/package.json
@@ -44,7 +44,9 @@
},
"devDependencies": {
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
+ "@swc/core": "^1.15.0",
"react-refresh": "^0.14.2",
+ "swc-loader": "^0.2.6",
"webpack-dev-server": "4"
}
}
diff --git a/spec/dummy/config/webpack/serverWebpackConfig.js b/spec/dummy/config/webpack/serverWebpackConfig.js
index 33af3e9eb1..e163f54d0c 100644
--- a/spec/dummy/config/webpack/serverWebpackConfig.js
+++ b/spec/dummy/config/webpack/serverWebpackConfig.js
@@ -45,6 +45,8 @@ const configureServer = () => {
serverWebpackConfig.output = {
filename: 'server-bundle.js',
globalObject: 'this',
+ // Note: libraryTarget should only be 'commonjs2' when using Node renderer
+ // For ExecJS (default), leave it undefined to allow proper evaluation
// If using the React on Rails Pro node server renderer, uncomment the next line
// libraryTarget: 'commonjs2',
path: config.outputPath,
@@ -107,8 +109,18 @@ const configureServer = () => {
// If using the default 'web', then libraries like Emotion and loadable-components
// break with SSR. The fix is to use a node renderer and change the target.
- // If using the React on Rails Pro node server renderer, uncomment the next line
- // serverWebpackConfig.target = 'node'
+ // React 19 requires target: 'node' to properly handle Node.js built-in modules
+ serverWebpackConfig.target = 'node';
+
+ // React 19 Fix: Prevent webpack from resolving to react-server condition
+ // The server-bundle needs the full React with hooks for SSR, not the react-server build
+ // Explicitly set conditionNames without react-server
+ if (!serverWebpackConfig.resolve) {
+ serverWebpackConfig.resolve = {};
+ }
+ // For target: 'node', webpack defaults to ['node', 'import', 'require', 'default']
+ // We explicitly list them to ensure react-server is not included
+ serverWebpackConfig.resolve.conditionNames = ['node', 'import', 'require', 'default'];
return serverWebpackConfig;
};
diff --git a/spec/dummy/package.json b/spec/dummy/package.json
index 958cd2df5c..90ca6222cb 100644
--- a/spec/dummy/package.json
+++ b/spec/dummy/package.json
@@ -35,6 +35,7 @@
"@babel/preset-react": "^7.10.4",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.1",
"@rescript/react": "^0.13.0",
+ "@swc/core": "^1.15.0",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@types/react-helmet": "^6.1.5",
@@ -53,6 +54,7 @@
"sass-resources-loader": "^2.1.0",
"shakapacker": "9.3.0",
"style-loader": "^3.3.1",
+ "swc-loader": "^0.2.6",
"terser-webpack-plugin": "5.3.1",
"url-loader": "^4.0.0",
"webpack": "5.72.0",
diff --git a/spec/dummy/yarn.lock b/spec/dummy/yarn.lock
index 861988c666..ed01f0e71a 100644
--- a/spec/dummy/yarn.lock
+++ b/spec/dummy/yarn.lock
@@ -1362,6 +1362,87 @@
dependencies:
"@sinonjs/commons" "^3.0.0"
+"@swc/core-darwin-arm64@1.15.0":
+ version "1.15.0"
+ resolved "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.0.tgz#158a0890fb2546b4d57b99234c1033e4a38b62e2"
+ integrity sha512-TBKWkbnShnEjlIbO4/gfsrIgAqHBVqgPWLbWmPdZ80bF393yJcLgkrb7bZEnJs6FCbSSuGwZv2rx1jDR2zo6YA==
+
+"@swc/core-darwin-x64@1.15.0":
+ version "1.15.0"
+ resolved "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.0.tgz#d03a71e60244f19ac921bf23c2cafc4122d76d8e"
+ integrity sha512-f5JKL1v1H56CIZc1pVn4RGPOfnWqPwmuHdpf4wesvXunF1Bx85YgcspW5YxwqG5J9g3nPU610UFuExJXVUzOiQ==
+
+"@swc/core-linux-arm-gnueabihf@1.15.0":
+ version "1.15.0"
+ resolved "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.0.tgz#fe978712a8924c0555c6b248ad3b57912ba123fb"
+ integrity sha512-duK6nG+WyuunnfsfiTUQdzC9Fk8cyDLqT9zyXvY2i2YgDu5+BH5W6wM5O4mDNCU5MocyB/SuF5YDF7XySnowiQ==
+
+"@swc/core-linux-arm64-gnu@1.15.0":
+ version "1.15.0"
+ resolved "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.0.tgz#a5dacdd857dec4ac2931820def17bc0e42c88ede"
+ integrity sha512-ITe9iDtTRXM98B91rvyPP6qDVbhUBnmA/j4UxrHlMQ0RlwpqTjfZYZkD0uclOxSZ6qIrOj/X5CaoJlDUuQ0+Cw==
+
+"@swc/core-linux-arm64-musl@1.15.0":
+ version "1.15.0"
+ resolved "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.0.tgz#243643a7d22c8e2f334046c1d76f342ad4369be9"
+ integrity sha512-Q5ldc2bzriuzYEoAuqJ9Vr3FyZhakk5hiwDbniZ8tlEXpbjBhbOleGf9/gkhLaouDnkNUEazFW9mtqwUTRdh7Q==
+
+"@swc/core-linux-x64-gnu@1.15.0":
+ version "1.15.0"
+ resolved "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.0.tgz#26936f55c916f65d33a4cf957c7573722f9eca54"
+ integrity sha512-pY4is+jEpOxlYCSnI+7N8Oxbap9TmTz5YT84tUvRTlOlTBwFAUlWFCX0FRwWJlsfP0TxbqhIe8dNNzlsEmJbXQ==
+
+"@swc/core-linux-x64-musl@1.15.0":
+ version "1.15.0"
+ resolved "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.0.tgz#a7164c11ac86ed99a1d5d8bef86ec0fbe6235f6c"
+ integrity sha512-zYEt5eT8y8RUpoe7t5pjpoOdGu+/gSTExj8PV86efhj6ugB3bPlj3Y85ogdW3WMVXr4NvwqvzdaYGCZfXzSyVg==
+
+"@swc/core-win32-arm64-msvc@1.15.0":
+ version "1.15.0"
+ resolved "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.0.tgz#645fe54564eab4224127672f2f4fe44876223af0"
+ integrity sha512-zC1rmOgFH5v2BCbByOazEqs0aRNpTdLRchDExfcCfgKgeaD+IdpUOqp7i3VG1YzkcnbuZjMlXfM0ugpt+CddoA==
+
+"@swc/core-win32-ia32-msvc@1.15.0":
+ version "1.15.0"
+ resolved "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.0.tgz#fd70c8c8b542a52a88cda758fb82569d52ea949a"
+ integrity sha512-7t9U9KwMwQblkdJIH+zX1V4q1o3o41i0HNO+VlnAHT5o+5qHJ963PHKJ/pX3P2UlZnBCY465orJuflAN4rAP9A==
+
+"@swc/core-win32-x64-msvc@1.15.0":
+ version "1.15.0"
+ resolved "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.0.tgz#1d4f06078c7dbf757c537dd08740472694257198"
+ integrity sha512-VE0Zod5vcs8iMLT64m5QS1DlTMXJFI/qSgtMDRx8rtZrnjt6/9NW8XUaiPJuRu8GluEO1hmHoyf1qlbY19gGSQ==
+
+"@swc/core@^1.15.0":
+ version "1.15.0"
+ resolved "https://registry.npmjs.org/@swc/core/-/core-1.15.0.tgz#6ae4dbd5a164261ba799ccdf9eae3bbc61e112c2"
+ integrity sha512-8SnJV+JV0rYbfSiEiUvYOmf62E7QwsEG+aZueqSlKoxFt0pw333+bgZSQXGUV6etXU88nxur0afVMaINujBMSw==
+ dependencies:
+ "@swc/counter" "^0.1.3"
+ "@swc/types" "^0.1.25"
+ optionalDependencies:
+ "@swc/core-darwin-arm64" "1.15.0"
+ "@swc/core-darwin-x64" "1.15.0"
+ "@swc/core-linux-arm-gnueabihf" "1.15.0"
+ "@swc/core-linux-arm64-gnu" "1.15.0"
+ "@swc/core-linux-arm64-musl" "1.15.0"
+ "@swc/core-linux-x64-gnu" "1.15.0"
+ "@swc/core-linux-x64-musl" "1.15.0"
+ "@swc/core-win32-arm64-msvc" "1.15.0"
+ "@swc/core-win32-ia32-msvc" "1.15.0"
+ "@swc/core-win32-x64-msvc" "1.15.0"
+
+"@swc/counter@^0.1.3":
+ version "0.1.3"
+ resolved "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9"
+ integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==
+
+"@swc/types@^0.1.25":
+ version "0.1.25"
+ resolved "https://registry.npmjs.org/@swc/types/-/types-0.1.25.tgz#b517b2a60feb37dd933e542d93093719e4cf1078"
+ integrity sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==
+ dependencies:
+ "@swc/counter" "^0.1.3"
+
"@types/babel__core@^7.1.14":
version "7.20.5"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017"
@@ -5968,6 +6049,13 @@ supports-preserve-symlinks-flag@^1.0.0:
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+swc-loader@^0.2.6:
+ version "0.2.6"
+ resolved "https://registry.npmjs.org/swc-loader/-/swc-loader-0.2.6.tgz#bf0cba8eeff34bb19620ead81d1277fefaec6bc8"
+ integrity sha512-9Zi9UP2YmDpgmQVbyOPJClY0dwf58JDyDMQ7uRc4krmc72twNI2fvlBWHLqVekBpPc7h5NJkGVT1zNDxFrqhvg==
+ dependencies:
+ "@swc/counter" "^0.1.3"
+
symbol-observable@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"