Appendix B: Migration Guides
From Create React App to Vite​
Create React App was deprecated in early 2025. Migrate to Vite:
Step 1: Replace Dependencies​
# Remove CRA
pnpm remove react-scripts
# Add Vite
pnpm add -D vite @vitejs/plugin-react
Step 2: Create vite.config.ts​
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
server: { port: 3000 },
});
Step 3: Move index.html​
Move public/index.html to the project root and update it:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>My App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Step 4: Update Environment Variables​
- Rename
REACT_APP_*toVITE_* - Replace
process.env.REACT_APP_*withimport.meta.env.VITE_*
Step 5: Update Scripts​
{
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview"
}
}
From React Router to TanStack Router​
Step 1: Install TanStack Router​
pnpm remove react-router-dom
pnpm add @tanstack/react-router
pnpm add -D @tanstack/router-plugin
Step 2: Convert Route Definitions​
React Router:
<Routes>
<Route path="/" element={<Home />} />
<Route path="/projects" element={<Projects />} />
<Route path="/projects/:id" element={<ProjectDetail />} />
</Routes>
TanStack Router (file-based):
src/routes/
├── index.tsx → /
├── projects/
│ ├── index.tsx → /projects
│ └── $id.tsx → /projects/:id
Step 3: Convert Hooks​
| React Router | TanStack Router |
|---|---|
useParams() | Route.useParams() |
useSearchParams() | Route.useSearch() |
useNavigate() | useNavigate() |
<Link to="/path"> | <Link to="/path"> |
useLocation() | useLocation() |
From Zod to Effect Schema​
Type Equivalents​
| Zod | Effect Schema |
|---|---|
z.string() | Schema.String |
z.number() | Schema.Number |
z.boolean() | Schema.Boolean |
z.object({}) | Schema.Struct({}) |
z.array(z.string()) | Schema.Array(Schema.String) |
z.enum(["a", "b"]) | Schema.Literal("a", "b") |
z.string().min(1) | Schema.String.pipe(Schema.minLength(1)) |
z.string().optional() | Schema.optional(Schema.String) |
z.string().nullable() | Schema.NullOr(Schema.String) |
z.infer<typeof schema> | typeof schema.Type |
schema.parse(data) | Schema.decodeUnknownSync(schema)(data) |
schema.safeParse(data) | Schema.decodeUnknownEither(schema)(data) |
Key Differences​
- Pipe syntax: Effect Schema uses
.pipe()for chaining refinements instead of method chaining - Branded types: Effect Schema has native branded type support via
Schema.brand() - Encoding: Effect Schema supports bidirectional transformations (decode AND encode)
- Integration: Effect Schema integrates naturally with Effect's error handling system
From Next.js to TanStack Start​
Key Differences​
| Concept | Next.js | TanStack Start |
|---|---|---|
| Routing | File-based (app router) | File-based (TanStack Router) |
| Data fetching | Server Components, fetch | Route loaders, TanStack Query |
| API routes | app/api/route.ts | Server functions |
| SSR | Automatic (app router) | Opt-in per route |
| Deployment | Vercel-optimized | Any Vite-compatible host |
| Type safety | Partial | End-to-end |
Migration Steps​
- Replace
nextwith@tanstack/startandvite - Convert
page.tsxfiles to TanStack Router route files - Replace
getServerSideProps/ Server Components with routeloaderfunctions - Replace Next.js
<Link>with TanStack Router<Link> - Replace
useRouter()withuseNavigate()andRoute.useParams() - Replace API routes with server functions or standalone API server
- Convert middleware to
beforeLoadguards