Key Takeaways
- Create React App is deprecated for new applications. Use current React framework guidance, or a build tool such as Vite when a framework isn't a good fit.
- For a new React TypeScript Redux setup, install Redux Toolkit and React-Redux rather than assembling a legacy Redux stack.
- Existing CRA applications can still be maintained, but assess dependency exposure and plan migration rather than treating CRA as a long-term tooling strategy.
- Ejecting exposes CRA's build configuration and has no built-in undo operation. It isn't required before migrating.
Create React App can host a React application with TypeScript and Redux, but it should not be your default for a new project in 2026. React deprecated CRA for new applications on February 14, 2025, and described its future as maintenance mode.
CRA originally made setup simple by hiding webpack, Babel, and related configuration behind a shared toolchain. That convenience explains why you'll encounter it in existing repositories; it no longer makes CRA the recommended starting point. The official CRA deprecation announcement explains what changed.
Choose: maintain CRA, migrate it, or start fresh
Use this decision tree as a practical starting point:
Starting point
+-- New React application
| +-- Want integrated application features? --> Evaluate a framework
| +-- Learning or deliberately client-only? --> Consider Vite
|
+-- Existing CRA application
+-- Bounded maintenance, reproducible build? --> Maintain with a migration plan
+-- Dependency or configuration blockers? --> Prioritize migration| Choice | What you gain | What you take on |
|---|---|---|
| Keep an existing CRA app temporarily | Avoid an immediate toolchain change | Deprecated tooling and dependency compatibility constraints |
| Use Vite for a client application | A development server and production build with configurable tooling | Your own routing, data-fetching, and testing choices |
| Use a React framework | Integrated application features and rendering options | The framework's conventions and deployment requirements |
React recommends starting new applications with a framework. Its guidance includes Next.js and React Router in framework mode; it also documents Vite, Parcel, and Rsbuild for projects built without a framework. Choose based on your application requirements, not because an older tutorial used a particular CLI.
Start a React TypeScript project with Vite
For this client-only example, use Vite's React TypeScript template. First install a supported Node.js release that meets the current Vite requirements, then run:
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run devOpen the address printed by the development server. For a walkthrough of the generated project, see our guide to creating a React app with Vite.
Vite supports TypeScript source files, but Vite itself transpiles TypeScript without type-checking it. Keep the generated template's TypeScript build step in your continuous integration pipeline; a working development page isn't proof that the project passes type checking.
Add Redux Toolkit to React and TypeScript
Redux stores shared application state outside individual components. An action describes an event, and a reducer calculates the resulting state. Use Redux when that shared-state model helps your application—not simply because a starter guide includes it.
The current installation is:
npm install @reduxjs/toolkit react-reduxRedux Toolkit's configureStore includes thunk middleware by default, so this setup doesn't require a separate redux-thunk installation. Redux Toolkit and React-Redux include their own TypeScript definitions.
The following files form a complete example for the Vite scaffold above. Stop the development server while editing, then restart it afterward. For an existing CRA repository, check React, TypeScript, and React-Redux compatibility first; retain its existing entry-file conventions rather than introducing Vite's file structure blindly.
1. Define a state slice
A slice groups one feature's state and update logic. createSlice generates action creators and a reducer; its Immer integration turns the apparent mutations below into immutable updates. That permission applies inside these reducers, not to arbitrary state mutations elsewhere.
// src/counterSlice.ts
import { createSlice } from '@reduxjs/toolkit';
import type { PayloadAction } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment(state) {
state.value += 1;
},
addByAmount(state, action: PayloadAction<number>) {
state.value += action.payload;
},
},
});
export const { increment, addByAmount } = counterSlice.actions;
export default counterSlice.reducer;2. Create the store and infer its types
configureStore combines the reducer map into the store's root reducer. Infer state and dispatch types from the store so they stay aligned with its configuration.
// src/store.ts
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
export const store = configureStore({
reducer: { counter: counterReducer },
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;3. Define typed React-Redux hooks
The current .withTypes() API creates hooks with your application's state and dispatch types already attached. Keep them in a separate module, following the official Redux TypeScript setup.
// src/hooks.ts
import { useDispatch, useSelector } from 'react-redux';
import type { AppDispatch, RootState } from './store';
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();4. Read state and dispatch actions
Replace src/App.tsx with this component. useAppSelector reads the counter value, and the click handlers dispatch the generated actions.
// src/App.tsx
import { addByAmount, increment } from './counterSlice';
import { useAppDispatch, useAppSelector } from './hooks';
export default function App() {
const count = useAppSelector((state) => state.counter.value);
const dispatch = useAppDispatch();
return (
<main>
<h2>Counter: {count}</h2>
<button type="button" onClick={() => dispatch(increment())}>
Add 1
</button>
<button type="button" onClick={() => dispatch(addByAmount(5))}>
Add 5
</button>
</main>
);
}5. Provide the store to React
React-Redux's Provider makes the store available to descendant components. Replace src/main.tsx with the following entry point, which mounts the application using createRoot.
// src/main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { Provider } from 'react-redux';
import App from './App';
import { store } from './store';
const container = document.getElementById('root');
if (!container) {
throw new Error('Missing #root container');
}
createRoot(container).render(
<StrictMode>
<Provider store={store}>
<App />
</Provider>
</StrictMode>,
);Run npm run dev again and exercise both buttons. In this example, the counter starts at zero and changes by one or five; it isn't persisted across a full page reload. Then check the production build and preview it locally:
npm run build
npm run previewMaintain an existing Create React App project
Recognize the legacy setup commands and templates
These are historical CRA scaffolding commands, included for interpreting older instructions or reproducing a legacy setup—not recommendations for a new application. Installation success still depends on the surrounding dependency and runtime compatibility.
# Historical JavaScript starter:
npx create-react-app legacy-js
# Historical TypeScript starter:
npx create-react-app legacy-ts --template typescriptThe commands scaffold the named directories. CRA's TypeScript template supplies TypeScript-oriented starter files and configuration; CRA also documented custom templates distributed through npm. A custom template doesn't change the underlying tool's deprecated status.
Add TypeScript to an existing CRA codebase carefully
Don't paste a modern compiler installation into an old dependency tree without checking compatibility. Inspect the installed react-scripts package's peer dependencies before selecting TypeScript and related type packages.
- Add compatible versions of
typescriptand the relevant definitions:@types/react,@types/react-dom, and, where needed,@types/nodeand@types/jest. These are the packages identified in CRA's historical TypeScript instructions. - Convert a small module first. Use
.tsxfor TypeScript files containing JSX and.tsfor files without JSX. Renaming files starts the transition; it doesn't add meaningful domain types or resolve incompatible code. - Create or inspect the root
tsconfig.json. CRA's tooling can generate this configuration when TypeScript is enabled; don't replace it wholesale with another tool's configuration. - Restart the development server, fix reported type errors, and run the tests and production build before expanding the conversion.
Our guide to TypeScript tsconfig.json explains the configuration model. If CRA's compiler constraints block needed upgrades, prioritize migration instead of forcing incompatible packages together.
Keep the existing scripts, but reassess the tooling
In a standard, unejected CRA project, start development with either of these commands, using the package manager already established by the repository:
npm start
# Or, for a repository using Yarn:
yarn startCRA's documented development address is localhost:3000. Its standard scripts include npm test for testing and npm run build for production output in the build directory.
For ongoing maintenance, preserve a reproducible lockfile, review direct and transitive dependency advisories, and distinguish browser exposure from build-system or CI exposure. Don't assume a deprecated tool will receive the upgrade cadence your application needs, and don't make re-scaffolding with CRA your normal upgrade strategy.
Understand what ejecting actually does
Historically, CRA offered this escape hatch:
npm run ejectEjection isn't required for advanced application code, and it isn't a prerequisite for moving away from CRA. Evaluate whether supported configuration options meet the immediate need; otherwise compare the cost of owning an ejected toolchain with migration.
Frontend tooling is not an application backend
CRA builds a client application; its development server isn't your application's backend, and CRA doesn't provide a built-in server-side rendering setup. Server-side rendering means producing the initial page HTML on a server rather than relying entirely on browser rendering.
A client application can call a separately deployed API. A framework such as Next.js or React Router in framework mode can provide frontend and server-side capabilities together. That doesn't mean every framework application needs a running server: rendering strategy and deployment mode determine that requirement.
Compare the tradeoffs in our explanation of client-side versus server-side rendering before choosing a replacement.
Migrate CRA without turning it into an application rewrite
Keep the first migration focused on tooling. Preserve component behavior and state structure, then change architecture separately if necessary. The official Rsbuild CRA migration guide is a useful reference for identifying CRA-specific assumptions, even when you're evaluating another destination.
Establish a baseline and replace build scripts
- Record the working runtime, lockfile, test results, production output, and deployment configuration before making changes.
- For Vite, use the new template as a reference for dependencies and configuration. Development normally moves from CRA's
npm starttonpm run dev. - Vite uses a root-level
index.htmlas an application entry point, unlike CRA's HTML template underpublic. Preserve the root container and wire up the appropriate module entry. - Before removing
react-scripts, replace every capability you still depend on: building, testing, linting, and type checking.
Translate environment variables and asset assumptions
CRA exposes custom browser variables through names such as process.env.REACT_APP_API_URL. Under Vite's default convention, the equivalent is import.meta.env.VITE_API_URL. Rename the environment entries and update their reads; don't replace every occurrence of process.env indiscriminately.
Inventory %PUBLIC_URL% references, SVG component imports, aliases, service-worker registration, browser targets, and custom build overrides. Map each one deliberately rather than assuming a successful home-page render proves migration is complete. Vite's development proxy is configured through server.proxy; review development API forwarding separately from production routing.
Make tests and deployment explicit
CRA manages a Jest test setup. Either configure Jest independently or migrate the suite to another runner; replacing the build tool doesn't port that configuration. If you choose Vitest, review its Jest migration guide, especially globals, mocks, and test-environment differences.
Keep tests focused on user-visible behavior, as described in our React component testing guide. For this counter example, cover the initial value and both button interactions; render it with a test store through Provider.
Update hosting and CI artifact paths: CRA's default production directory is build, while Vite's is dist. Verify direct navigation to nested routes, asset URLs, deployment subpaths, and environment-specific builds. Vite's preview command is for local inspection, not production serving.
Before cutover, require passing type checks, lint checks, tests, and a production-build smoke test. Keep a deployable rollback until the migrated application has passed those checks in its actual hosting environment.
FAQ
Is Create React App deprecated?
Yes. React deprecated Create React App for new applications on February 14, 2025, and described it as continuing in maintenance mode. Existing applications don't stop working because of that announcement, but new projects should use current alternatives.
What should I use instead of Create React App?
Start with React's framework guidance, including Next.js and React Router in framework mode. If a framework isn't a good fit, React documents build-tool options including Vite, Parcel, and Rsbuild.
How do I add Redux Toolkit to an existing CRA app?
Check dependency compatibility, then install compatible versions of @reduxjs/toolkit and react-redux. Create a store with configureStore, define feature state with createSlice, and wrap your application in Provider while retaining the entry point appropriate to your installed React version.
Can I migrate a CRA app without rewriting it?
A tooling migration can preserve your components and Redux logic while replacing the build setup. Review scripts, environment variables, HTML and asset handling, tests, and deployment configuration; custom overrides can make the work more involved.
Do I need to eject before migrating CRA?
No. Migration doesn't require ejection. Ejection copies CRA's configuration and build dependencies into your repository and has no built-in undo operation, so it shouldn't be a routine migration step.
Further Reading
- React: Sunsetting Create React App — deprecation, maintenance status, and replacement paths.
- React: Creating a React App — framework choices and when to build without one.
- Vite: Getting Started — prerequisites, templates, and development commands.
- Redux Quick Start — Redux Toolkit, Provider, and component integration.
- Redux: Usage with TypeScript — inferred store types and typed hooks.
- CRA: Adding TypeScript — historical instructions for existing projects.
- Rsbuild: Migrating from CRA — scripts, assets, configuration, and compatibility checks.
- Vitest: Migrating from Jest — test-runner differences to review during migration.
