Upgrade Your Entire Stack to 2026 Models in One Weekend

Step-by-step guide to upgrading React, TypeScript, Rust, and infrastructure to the latest 2026 versions without breaking production.

Problem: Your Stack Is Running on Last Year's Defaults

You're on React 18, TypeScript 5.3, and Node 20. Everything works — but you're missing meaningful performance gains, new primitives, and security patches. Upgrading piecemeal leads to version mismatches that are harder to debug than a full planned migration.

You'll learn:

  • How to audit your current stack and identify safe upgrade paths
  • The exact sequence to upgrade frontend, backend, and infra without downtime
  • What actually changed in 2026 models and what you can skip

Time: 60 min | Level: Advanced


Why This Happens

Stacks drift. You pin versions for stability, ship features, and suddenly you're three major versions behind. In 2026, this matters more than it used to — React 19's compiler, TypeScript 5.7's stricter inference, and Node 22 LTS aren't cosmetic upgrades. They change how your code runs.

Common symptoms:

  • Bundle sizes 20-30% larger than peers using newer tooling
  • Type errors suppressed with any that newer TS would catch automatically
  • Missing security patches (Node 20 EOL hit in April 2026)
  • CI pipelines taking longer because of outdated build tooling

Solution

Step 1: Audit Before You Touch Anything

Run a full dependency snapshot first. You want a rollback point and a clear picture of what's outdated.

# Snapshot current state
node -v > .stack-audit.txt
npm -v >> .stack-audit.txt
npx tsc --version >> .stack-audit.txt

# Check outdated packages
npm outdated >> .stack-audit.txt

# Check for known vulnerabilities
npm audit >> .stack-audit.txt

Then check your actual usage of deprecated APIs — this is where most breakage hides:

# Install the migration linter for React 19
npx react-codemod@latest --list

# TypeScript: run strict mode as a dry-run without changing tsconfig
npx tsc --strict --noEmit 2> ts-strict-errors.txt
wc -l ts-strict-errors.txt

Expected: You'll get a count of strict-mode violations. Under 50 is a quick weekend fix. Over 200 means plan two weekends and fix TS first.

If it fails:

  • tsc not found: Run npm install -D typescript@latest first
  • codemod crashes on JSX: Add --parser=tsx flag

Step 2: Upgrade Node and Package Manager

Node 22 LTS is the 2026 baseline. Upgrade the runtime before touching framework versions.

# Using nvm (recommended)
nvm install 22
nvm use 22
nvm alias default 22

# Verify
node -v  # Should show v22.x.x

# Update npm to v11
npm install -g npm@11

# If you use pnpm (faster for monorepos)
npm install -g pnpm@9

After switching Node versions, rebuild native dependencies:

# Clears and rebuilds node_modules for the new Node ABI
npm rebuild

# Or with pnpm
pnpm rebuild

Expected: node -v returns v22.x.x, no ABI mismatch errors on npm start.

If it fails:

  • ABI mismatch on better-sqlite3 or similar native modules: Run npm rebuild <package-name> individually
  • Scripts fail with ERR_UNKNOWN_FILE_EXTENSION: Your package.json is missing "type": "module" — either add it or rename files to .cjs

Step 3: Upgrade TypeScript

Go to 5.7 directly. Don't stop at 5.5 or 5.6 — 5.7 includes the narrowing improvements that make the strict-mode fixes from Step 1 much easier.

npm install -D typescript@5.7 @types/node@22

Update tsconfig.json for 2026 defaults:

{
  "compilerOptions": {
    "target": "ES2024",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": ["ES2024", "DOM", "DOM.Iterable"],

    // 2026 strict defaults — enable these one at a time if migrating legacy code
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,

    // TS 5.7 performance — enables incremental with isolated declarations
    "isolatedDeclarations": true,
    "incremental": true,

    "skipLibCheck": false,  // Stop skipping — fix your types
    "outDir": "./dist",
    "rootDir": "./src"
  }
}

Run the compiler and fix errors in this order: 1) null/undefined issues, 2) implicit any, 3) exact optional properties. Don't fix them all at once — commit after each category.

npx tsc --noEmit

Expected: Zero errors before moving to the next step.


Step 4: Upgrade React and Next.js

React 19 and Next.js 15 go together. Don't upgrade one without the other.

npm install react@19 react-dom@19 next@15
npm install -D @types/react@19 @types/react-dom@19

Run the official codemods — they handle 80% of the mechanical changes:

# Removes deprecated lifecycle method patterns
npx react-codemod@latest rename-unsafe-lifecycles src/

# Updates to new JSX transform (removes React import from every file)
npx @next/codemod@latest next-async-request-api .

# Updates dynamic imports and metadata API
npx @next/codemod@latest metadata-to-viewport-export .

The two things codemods won't fix — you handle manually:

// 1. forwardRef is gone — use ref as a regular prop
// Before (React 18)
const Input = forwardRef<HTMLInputElement, Props>((props, ref) => (
  <input ref={ref} {...props} />
));

// After (React 19)
function Input({ ref, ...props }: Props & { ref?: React.Ref<HTMLInputElement> }) {
  return <input ref={ref} {...props} />;
}

// 2. Context no longer needs .Provider — use it directly
// Before
<ThemeContext.Provider value={theme}>

// After
<ThemeContext value={theme}>

Expected: next dev starts without warnings, pages render correctly.

If it fails:

  • useFormState not found: It's now useActionState — the codemod should have caught it, but check manually
  • Hydration errors after upgrade: React 19's stricter hydration catches mismatches that 18 silently ignored — fix the underlying mismatch in your server/client rendering

Step 5: Update Infrastructure

Docker base images and CI runners are easy to overlook. Mismatched runtime versions between local and production cause bugs that are painful to trace.

Update your Dockerfile:

# Before
FROM node:20-alpine

# After — use the slim variant for smaller images
FROM node:22-alpine3.19

WORKDIR /app
COPY package*.json ./

# Use cache mount for faster builds in 2026 Docker BuildKit
RUN --mount=type=cache,target=/root/.npm \
    npm ci --production

COPY . .
RUN npm run build

EXPOSE 3000
CMD ["node", "dist/server.js"]

Update GitHub Actions:

# .github/workflows/ci.yml
jobs:
  build:
    runs-on: ubuntu-24.04  # 22.04 goes EOL mid-2026
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      - run: npm test

Verification

Run your full suite end-to-end:

# Type check
npx tsc --noEmit

# Unit tests
npm test

# Build
npm run build

# E2E (if you have Playwright)
npx playwright test

# Check bundle size didn't regress
npx bundlesize

You should see: All tests pass, build completes, bundle size is equal to or smaller than pre-upgrade (React 19 compiler typically reduces JS by 15-25%).


What You Learned

  • Audit before upgrading — the strict-mode error count tells you how much time to budget
  • Node runtime first, then TypeScript, then frameworks — order matters
  • Codemods handle the mechanical work; forwardRef removal and Context API changes need manual review
  • Infrastructure (Docker, CI) must match your local Node version or you'll ship bugs that only appear in production

Limitations: This guide covers the React/Next.js + TypeScript path. Go, Rust, and Python backend upgrades follow the same audit-first principle but have different breaking change profiles — worth a separate pass.

When NOT to use this approach: If you're mid-feature freeze or within two weeks of a major release, wait. A planned stack upgrade done in a quiet sprint is always better than an emergency rollback.


Tested on macOS Sequoia 15.3, Ubuntu 24.04 LTS — Node 22.14, TypeScript 5.7.3, React 19.0, Next.js 15.2