Detailed upgrade procedures for major framework version transitions.
[ ] Running React 18.3.x (last 18.x with deprecation warnings)
[ ] All deprecation warnings resolved
[ ] No usage of legacy string refs
[ ] No usage of legacy context (contextTypes)
[ ] No usage of defaultProps on function components
[ ] No usage of propTypes at runtime
[ ] Test suite passing on 18.3.x
[ ] TypeScript 5.x or later (for type changes)
Run the official codemod:
npx codemod@latest react/19/migration-recipe --target src/
Update package.json:
npm install react@19 react-dom@19
npm install -D @types/react@19 @types/react-dom@19
Update react-dom entry point:
// Before (React 18)
import { createRoot } from 'react-dom/client';
// After (React 19) -- same API, but check for removed APIs below
Run tests and fix remaining issues.
| Removed API | Replacement |
|---|---|
forwardRef |
Pass ref as a regular prop |
<Context.Provider> |
Use <Context> directly as provider |
defaultProps on function components |
Use JS default parameters |
propTypes runtime checking |
Use TypeScript or Flow |
react-test-renderer |
Use @testing-library/react |
ReactDOM.render |
Use createRoot (already required in 18) |
ReactDOM.hydrate |
Use hydrateRoot (already required in 18) |
unmountComponentAtNode |
Use root.unmount() |
ReactDOM.findDOMNode |
Use refs |
// use() hook -- read promises and context in render
import { use } from 'react';
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise);
return <h1>{user.name}</h1>;
}
// useActionState -- form action with state
import { useActionState } from 'react';
function LoginForm() {
const [state, formAction, isPending] = useActionState(
async (prev: State, formData: FormData) => {
const result = await login(formData);
return result;
},
{ error: null }
);
return <form action={formAction}>...</form>;
}
// useOptimistic -- optimistic updates
import { useOptimistic } from 'react';
function TodoList({ todos }: { todos: Todo[] }) {
const [optimisticTodos, addOptimistic] = useOptimistic(
todos,
(state, newTodo: Todo) => [...state, newTodo]
);
// ...
}
// ref as prop -- no more forwardRef
function Input({ ref, ...props }: { ref?: React.Ref<HTMLInputElement> }) {
return <input ref={ref} {...props} />;
}
// Context as provider
const ThemeContext = createContext('light');
// Before: <ThemeContext.Provider value="dark">
// After:
<ThemeContext value="dark">
<App />
</ThemeContext>
# Run type checking
npx tsc --noEmit
# Run tests
npm test
# Search for removed APIs that codemods may have missed
rg "forwardRef" src/
rg "Context\.Provider" src/
rg "defaultProps" src/ --glob "*.tsx"
rg "propTypes" src/ --glob "*.tsx"
rg "findDOMNode" src/
rg "react-test-renderer" package.json
[ ] Running latest Next.js 14.x or 15.x
[ ] Understood Server vs Client Component model
[ ] Identified pages that need client-side interactivity
[ ] Reviewed data fetching strategy (no more getServerSideProps/getStaticProps)
[ ] Identified API routes that need migration
[ ] Middleware already using edge runtime (if applicable)
app/ directory alongside existing pages/.Create app/layout.tsx (replaces _app.tsx and _document.tsx):
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
Migrate pages one at a time -- both routers work simultaneously.
Convert data fetching:
// Before (Pages Router)
export async function getServerSideProps() {
const data = await fetchData();
return { props: { data } };
}
export default function Page({ data }) { ... }
// After (App Router)
export default async function Page() {
const data = await fetchData(); // direct async component
return <div>{data}</div>;
}
Convert dynamic routes:
pages/posts/[id].tsx → app/posts/[id]/page.tsx
pages/[...slug].tsx → app/[...slug]/page.tsx
Run the official codemod:
npx @next/codemod@latest
| Pages Router | App Router | Purpose |
|---|---|---|
pages/index.tsx |
app/page.tsx |
Home page |
pages/about.tsx |
app/about/page.tsx |
Static page |
pages/posts/[id].tsx |
app/posts/[id]/page.tsx |
Dynamic page |
pages/_app.tsx |
app/layout.tsx |
Root layout |
pages/_document.tsx |
app/layout.tsx |
HTML document |
pages/_error.tsx |
app/error.tsx |
Error boundary |
pages/404.tsx |
app/not-found.tsx |
Not found page |
pages/api/hello.ts |
app/api/hello/route.ts |
API route |
| N/A | app/loading.tsx |
Loading UI (new) |
| N/A | app/template.tsx |
Re-mounted layout (new) |
| Pages Router | App Router |
|---|---|
getServerSideProps |
async Server Component (fetches on every request) |
getStaticProps |
async Server Component + fetch with cache: 'force-cache' |
getStaticPaths |
generateStaticParams() |
getInitialProps |
Remove entirely -- use Server Components |
useRouter().query |
useSearchParams() (client) or searchParams prop (server) |
useRouter from next/navigation not next/routerpathname no longer includes query parametersLink no longer requires <a> childImage component default behavior changes<Head> component# Check for Pages Router imports in migrated files
rg "from 'next/router'" app/
rg "getServerSideProps|getStaticProps|getInitialProps" app/
rg "next/head" app/
# Verify all routes work
npm run build # catches most issues at build time
npm run dev # test interactive behavior
[ ] Identified all breaking syntax changes (v-model, filters, events)
[ ] Listed third-party Vue 2 plugins that need Vue 3 equivalents
[ ] Decided on migration approach: migration build (@vue/compat) vs direct
[ ] Decided on state management: Vuex → Pinia migration
[ ] Test suite passing on Vue 2
<script setup>).Switch to Vue 3 + @vue/compat:
npm install vue@3 @vue/compat
Configure compat mode in bundler (Vite or Webpack):
// vite.config.js
export default {
resolve: {
alias: { vue: '@vue/compat' }
}
};
Fix compatibility warnings one category at a time.
Remove @vue/compat once all warnings are resolved.
| Vue 2 | Vue 3 | Notes |
|---|---|---|
v-model (default) |
v-model uses modelValue prop + update:modelValue event |
Custom model option removed |
v-bind.sync |
v-model:propName |
.sync modifier removed |
Filters {{ value \| filter }} |
Methods or computed | Filters removed entirely |
$on, $off, $once |
External library (mitt) | Event bus pattern removed |
Vue.component() global |
app.component() |
Global API restructured |
Vue.use() |
app.use() |
Plugin installation |
Vue.mixin() |
app.mixin() or Composition API |
Global mixins |
Vue.filter() |
N/A | Filters removed |
this.$set / Vue.set |
Direct assignment | Reactivity system rewritten (Proxy-based) |
this.$delete / Vue.delete |
delete obj.prop |
Proxy handles this |
$listeners |
Merged into $attrs |
Separate $listeners removed |
$children |
Template refs | Direct child access removed |
<transition> class names |
v-enter-from / v-leave-from |
-active suffix retained |
// Vuex (old)
const store = createStore({
state: { count: 0 },
mutations: { increment(state) { state.count++; } },
actions: { asyncIncrement({ commit }) { commit('increment'); } },
getters: { doubleCount: (state) => state.count * 2 }
});
// Pinia (new)
export const useCounterStore = defineStore('counter', () => {
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
function increment() { count.value++; }
async function asyncIncrement() { increment(); }
return { count, doubleCount, increment, asyncIncrement };
});
# Vue official codemod
npx @vue/codemod src/
# Specific transforms
npx @vue/codemod src/ --transform vue-class-component-v8
npx @vue/codemod src/ --transform new-global-api
npx @vue/codemod src/ --transform vue-router-v4
# Search for Vue 2 patterns
rg "\$on\(|\.sync|Vue\.component|Vue\.use|Vue\.mixin" src/
rg "this\.\$set|this\.\$delete|this\.\$children" src/
rg "filters:" src/ --glob "*.vue"
rg "v-bind\.sync" src/ --glob "*.vue"
# Build and test
npm run build
npm test
[ ] Running PHP 8.2+ (Laravel 11 requires PHP 8.2 minimum)
[ ] All tests passing on Laravel 10
[ ] Reviewed Laravel 11 release notes
[ ] Identified custom service providers that need updates
[ ] Checked third-party package Laravel 11 compatibility
Use Laravel Shift (recommended, paid automated service):
https://laravelshift.com
Or manual upgrade -- update composer.json:
{
"require": {
"laravel/framework": "^11.0"
}
}
Run composer update:
composer update
Apply skeleton changes (Laravel 11 uses a slimmer skeleton):
bootstrap/app.php is simplifiedconfig/ (use defaults)bootstrap/app.phpapp/Http/Kernel.php removedFix deprecation warnings and test.
| Laravel 10 | Laravel 11 | Notes |
|---|---|---|
app/Http/Kernel.php |
bootstrap/app.php |
Middleware registration moved |
| Multiple service providers | Single AppServiceProvider |
Consolidated providers |
Full config/ directory |
Minimal config (publish as needed) | php artisan config:publish to restore |
Console Kernel.php |
routes/console.php with closures |
Schedule defined in routes/console.php |
| Exception handler class | bootstrap/app.php withExceptions() |
Exception handling consolidated |
$schedule->command()->everyMinute() |
->everySecond() now available |
Per-second scheduling added |
| Explicit casts property | casts() method on model |
Method-based casting |
// Per-second scheduling
Schedule::command('check:pulse')->everySecond();
// Dumpable trait
use Illuminate\Support\Traits\Dumpable;
class MyService {
use Dumpable;
// Now supports ->dd() and ->dump() chaining
}
// Method-based casts
protected function casts(): array {
return [
'options' => AsArrayObject::class,
'created_at' => 'datetime:Y-m-d',
];
}
// Simplified bootstrap/app.php
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
api: __DIR__.'/../routes/api.php',
)
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [CustomMiddleware::class]);
})
->withExceptions(function (Exceptions $exceptions) {
$exceptions->report(function (SomeException $e) {
// custom reporting
});
})
->create();
# Check for removed patterns
rg "class Kernel extends HttpKernel" app/
rg "class Handler extends ExceptionHandler" app/
# Run tests
php artisan test
# Check config
php artisan config:show
# Verify routes
php artisan route:list
[ ] Check Angular Update Guide: https://update.angular.io
[ ] Running the latest patch of current major version
[ ] All tests passing
[ ] No deprecated APIs in use (check ng build warnings)
[ ] Third-party libraries checked for target version compatibility
Check the update guide for your specific version jump:
https://update.angular.io/?from=16.0&to=17.0
Run ng update for core packages:
ng update @angular/core @angular/cli
Run ng update for additional Angular packages:
ng update @angular/material # if using Material
ng update @angular/router # if needed
Review and apply schematics that ng update runs automatically.
Fix any remaining issues and run tests.
| Version | Key Changes |
|---|---|
| 14 | Standalone components, typed forms, inject() function |
| 15 | Standalone APIs stable, directive composition, image optimization |
| 16 | Signals (developer preview), required inputs, esbuild builder |
| 17 | Signals stable, deferrable views, built-in control flow, esbuild default |
| 18 | Zoneless change detection (experimental), Material 3, fallback content |
| 19 | Standalone by default, linked signals, resource API, incremental hydration |
ng build --configuration=production
ng test
ng e2e
# Check for deprecation warnings in build output
ng build 2>&1 | rg -i "deprecated|warning"
[ ] Running the latest patch of current major version
[ ] All deprecation warnings resolved
[ ] Tests passing with python -Wd (warnings as errors)
[ ] Third-party packages checked for target version support
[ ] Database migrations up to date
Enable deprecation warnings:
python -Wd manage.py test
Fix all deprecation warnings on current version.
Read release notes for target version:
https://docs.djangoproject.com/en/5.0/releases/
Update Django:
pip install Django==5.0
Run django-upgrade codemod:
pip install django-upgrade
django-upgrade --target-version 5.0 $(fd -e py)
Run tests and fix issues.
| Version | Key Changes |
|---|---|
| 4.0 | Redis cache backend, scrypt hasher, template-based form rendering |
| 4.1 | Async ORM, async view support, validation of model constraints |
| 4.2 | Psycopg 3, STORAGES setting, custom file storage |
| 5.0 | Facet filters in admin, simplified templates, database-computed default |
| 5.1 | LoginRequiredMiddleware, connection pool for PostgreSQL |
# django-upgrade: automated fixes
pip install django-upgrade
django-upgrade --target-version 5.0 **/*.py
# Specific transforms handled:
# - url() to path() in urlconfs
# - @admin.register decorator
# - HttpResponse charset parameter
# - Deprecated model field arguments
# Full test suite with warnings
python -Wd manage.py test
# Check for deprecated imports
rg "from django.utils.encoding import force_text" .
rg "from django.conf.urls import url" .
rg "from django.utils.translation import ugettext" .
# Verify migrations
python manage.py makemigrations --check
python manage.py migrate --run-syncdb
# Check system
python manage.py check --deploy
Regardless of which framework you are upgrading, follow this universal checklist after the migration is complete:
Post-Migration Verification
│
├─ [ ] All tests pass (unit, integration, e2e)
├─ [ ] Build succeeds in production mode
├─ [ ] No deprecation warnings in build output
├─ [ ] Bundle size compared to pre-migration baseline
├─ [ ] Performance benchmarks compared to pre-migration baseline
├─ [ ] Error monitoring shows no new error types
├─ [ ] All pages/routes load correctly (smoke test)
├─ [ ] Forms and user interactions work
├─ [ ] Authentication and authorization work
├─ [ ] Third-party integrations verified
├─ [ ] CI/CD pipeline updated for new version
├─ [ ] Docker/deployment images updated
├─ [ ] Documentation updated (README, setup guide)
└─ [ ] Team notified of completed migration