This document outlines the steps required to migrate our application from React 17 with Create React App (CRA) to React 18 with Vite. All commands should be run from the client directory, and all file modifications should be made to files within the client directory.
# Update React core packages
npm install --save "react@^19" "react-dom@^19" "@types/react@^19" "@types/react-dom@^19"If your project uses fortawesome, upgrade the fortawesome packages to 6.7.2 or higher
npm install --save "@fortawesome/fontawesome-svg-core@latest" "@fortawesome/free-brands-svg-icons@latest" "@fortawesome/free-regular-svg-icons@latest" "@fortawesome/free-solid-svg-icons@latest" "@fortawesome/react-fontawesome@latest"# Update React Testing Library
npm install --save-dev @testing-library/react@latestThe main change in React 19 is the new root API. We updated our index.tsx to use the new API:
// Old React 17 way
import ReactDOM from 'react-dom';
// ...
ReactDOM.render(
<React.StrictMode>
<AppWrapper>
<App />
</AppWrapper>
</React.StrictMode>,
document.getElementById('root')
);
// New React 19 way
import ReactDOM from 'react-dom/client';
// ...
const rootElement = document.getElementById('root') as HTMLElement;
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<AppWrapper>
<App />
</AppWrapper>
</React.StrictMode>,
);npm uninstall --save react-scriptsnpm install --save-dev @types/node@latestnpm install --save-dev vite @vitejs/plugin-react vite-tsconfig-paths vite-plugin-node-polyfillsAdd the following to your .eslintrc.js file:
ignorePatterns: [
'vite.config.*',
],Create vite.config.mjs in the client directory:
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { nodePolyfills } from 'vite-plugin-node-polyfills';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
export default defineConfig({
plugins: [
react(),
nodePolyfills({
// Whether to polyfill `node:` protocol imports
protocolImports: true,
}),
],
resolve: {
alias: {
'src': resolve(__dirname, 'src')
},
},
server: {
port: 3000,
open: false,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
}
}
},
build: {
outDir: 'build',
}
});Copy the existing index.html from the public folder to the client root directory and update it:
Then update the copied file to:
- Replace any
%PUBLIC_URL%references with/ - Add the script tag for the main entry point: Example:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Hello Harvard: a system for getting your course set up" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>Hello Harvard</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>Replace CRA scripts with Vite commands:
"scripts": {
"dev:client": "vite",
"start": "vite",
"build": "npm i --production=false && vite build",
"build-dev": "vite build --mode development",
"auto-build": "vite build --watch",
"preview": "vite preview",
"test": "vitest run"
}# Install Vitest and related packages
npm install --save-dev vitest jsdomCreate vitest.config.ts file:
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
globals: true,
environment: 'jsdom',
setupFiles: './src/setupTests.ts',
},
});You should replace all @import statements with @use statements in your SCSS files. Also, variables from shared.scss must now be accessed as shared.$variable-name.
// Old way with @import
@import './shared.scss';
.my-component {
color: $primary-color;
margin: div(16px, 2);
}
// New way with @use
@use './shared.scss';
.my-component {
color: shared.$primary-color;
margin: math.div(16px, 2);
}Update the file to be this:
/// <reference types="react-scripts" />
declare module '*.svg' {
const content: string;
export default content;
}
/// <reference types="react-scripts" />
// Allow SVG images
declare module '*.svg' {
const content: string;
export default content;
}
// Allow SCSS imports
declare module '*.scss';