<aside> ⚠️
The guide is written to upgrade:
Starting from earlier versions might work, but it's not guaranteed (it will probably need extra work). Read the previous upgrade guide first.
</aside>
<aside> 🎏
References:
<aside> 💁
This guide upgrades the framework only. It does NOT migrate to Signals or Zoneless change detection. Angular 22 makes OnPush the default, but the ng update migration stamps ChangeDetectionStrategy.Eager on existing components to preserve the current behaviour — keep any stamps it adds (on a Zone-based app that doesn't opt into the new default it may report no changes, which is also fine). Adopting signals + OnPush/zoneless is a separate, future migration. Fore more info, you can read a clear explanation here. Finally, here you can follow a guide to migrate IDEA projects to Signal+Zoneless.
</aside>
<aside> 🆚
Before to start, make sure your developer tools are updated to the versions stated in our Development tools versions guide — note that almost every tool has been updated.
</aside>
<aside> 👉
Commits of reference/example:
Align package.json to the block below, then npm i. Add only packages your project uses; some may be missing. Also bump these IDEA libs when present (check their release notes): idea-toolbox ^7.3.0, idea-calendar-connectors ^1.2.0, idea-html2pdf ^2.3.0, idea-aws ^4.6.1.
"engines": {
"node": ">=24.0.0"
},
"dependencies": {
"idea-aws": "^4.6.1",
"idea-toolbox": "^7.3.0"
},
"devDependencies": {
"@apidevtools/swagger-parser": "^12.1.0",
"@eslint/eslintrc": "^3.3.5",
"@eslint/js": "^10.0.1",
"@iter-idea/ses-verify-identities": "^1.0.2",
"@tsconfig/node24": "^24.0.4",
"@types/node": "^24.13.2",
"@typescript-eslint/eslint-plugin": "^8.62.0",
"@typescript-eslint/parser": "^8.62.0",
"aws-cdk": "^2.1128.1",
"aws-cdk-lib": "^2.260.0",
"constructs": "^10.6.0",
"esbuild": "^0.28.1",
"eslint": "^10.5.0",
"globals": "^17.7.0",
"tsx": "^4.22.4",
"typescript": "^6.0.3"
}
Add a .nvmrc file containing 24 at the repository root. In a monorepo a single root .nvmrc covers every package (back-end, front-end, tracker, …) — do not add a per-folder .nvmrc.
tsconfig.json (back-end and scripts): set extends to @tsconfig/node24; remove any "lib" in compilerOptions (leave target); add "ignoreDeprecations": "6.0" (TS6 turns the deprecated moduleResolution: "node10" into an error).
<aside> 🧰
Tooling packages with their own package.json (scripts, tracker, …) need the same bumps as the back-end, not just the tsconfig: @tsconfig/node24, @types/node ^24, typescript ^6. If one imports cross-project (e.g. scripts importing ../../back-end/src/models), TS6 can stop auto-discovering Node's globals → Cannot find name 'console' (TS2584); pin them explicitly with "types": ["node"] in compilerOptions.
</aside>
cdk.json: set "app": "npx tsx deploy/main.ts" and add to context "@aws-cdk/core:defaultCrossStackReferences": "strong".
<aside> 🍄
ts-node (unmaintained) is incompatible with TS6 — remove it from devDependencies. tsx only runs the CDK app (transpile-only); type-checking still happens via npm run compile, the linter and CI.
</aside>
Lambda runtime → v24 (all stacks): NODEJS_22_X → NODEJS_24_X (CDK), nodejs22.x → nodejs24.x (SAM), or via the Lambda console (non-IaC).
<aside> 🪛
Non-IaC back-ends (script-deployed projects). If the back-end has no CDK/SAM (deploy via a script + AWS CLI, e.g. deploy.sh / updateLambda.sh), skip the IaC steps entirely — cdk.json (step 4), the deprecated-CDK-API migration (step 6), media-stack.ts (step 7), certificates, and tsx. Just set each Lambda's runtime to nodejs24.x from the console, or:
aws lambda update-function-configuration --function-name <fn> --runtime nodejs24.x
</aside>
Migrate the deprecated CDK APIs:
pointInTimeRecovery: true → pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: true } on DynamoDB tables (zero-diff).
DnsValidatedCertificate → Certificate with validation: CertificateValidation.fromDns(zone), only for regional certificates (e.g. API Gateway). Leave the CloudFront certificates with region: 'us-east-1'.
<aside> ⚠️
Switching from DnsValidatedCertificate (a custom resource) to the native Certificate replaces the certificate on next deploy: a new one is issued and DNS-validated automatically (hosted zone in Route53). Expected and safe, but a replacement, not in-place.
</aside>
Prebuilt IDEA Lambda artifacts (thumbnailer + html2pdf) — pin them by runtime-scoped key + an auto-hashed currentVersion alias (so a key change republishes the version and repoints the alias the S3 event invokes), in media-stack.ts:
media-stack.ts<aside> ⚠️
Node 24 removed callback-style handlers → the old flat zips crash with Runtime.CallbackHandlerDeprecated; and Code.fromBucket() without an objectVersion never re-pulls a same-key overwrite. The node24/ artifacts — functions and layers — must exist in every regional idea-lambda-functions bucket, or cdk deploy fails. After deploying, test PDF printing (previews, email attachments) and image/PDF thumbnails; issues → @Matteo Carbone.
</aside>
<aside> 🍱
SAM / non-CDK back-ends: there's no media-stack.ts. Apply the same pinning in the SAM template (template-thumbnailer.yaml): point the prebuilt artifacts at the runtime-scoped keys and bump the runtime — CodeUri: s3://idea-lambda-functions/node24/fn-thumbnailer.zip, each layer's ContentUri to …/node24/layer-ghost-script.zip and …/node24/layer-image-magick.zip, and Runtime / CompatibleRuntimes / BuildMethod → nodejs24.x. AutoPublishAlias: prod already republishes the version on each key change, so the S3 event keeps invoking the fresh code.
</aside>
ESLint 9 → 10: the no-longer-transitive globals / @eslint/js / @eslint/eslintrc are already in the block above; fix the new rules it flags. Two fire in practice: no-useless-assignment — (tot, acc) => (tot += acc) becomes (tot, acc) => tot + acc; and preserve-caught-error — throwing a new error inside a catch is flagged, so either chain the cause (throw new Error(msg, { cause: e })) or throw a non-native error class (e.g. IDEA's HandledError), which the rule doesn't flag.
Run npm i, then deploy the back-end.
<aside> 👉
Reference commit (updating front-end): https://github.com/iter-idea/unindustria/commit/2128faccaeecffd9e50881874cd8c239a799fcd7
</aside>
Target package.json — align to the block below (ng update sets most of it; add only what you use):
"engines": {
"node": ">=24.15.0"
},
"dependencies": {
"@angular/animations": "^22.0.2",
"@angular/common": "^22.0.2",
"@angular/core": "^22.0.2",
"@angular/forms": "^22.0.2",
"@angular/platform-browser": "^22.0.2",
"@angular/platform-browser-dynamic": "^22.0.2",
"@angular/router": "^22.0.2",
"@capacitor/app": "^8.0.1",
"@capacitor/browser": "^8.0.3",
"@capacitor/core": "^8.4.1",
"@idea-ionic/auth": "^8.12.0",
"@idea-ionic/common": "^8.12.0",
"@ionic/angular": "^8.8.11",
"@ionic/storage-angular": "^4.0.0",
"idea-toolbox": "^7.3.0",
"ionicons": "^8.0.13",
"qrcode": "^1.5.4",
"rxjs": "~7.8.0",
"tslib": "^2.4.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "^22.0.4",
"@angular-eslint/builder": "^22.0.0",
"@angular-eslint/schematics": "^22.0.0",
"@angular/cli": "^22.0.4",
"@angular/compiler": "^22.0.2",
"@angular/compiler-cli": "^22.0.2",
"@capacitor/cli": "^8.4.1",
"@ionic/angular-toolkit": "^12.3.0",
"@types/node": "^24.13.2",
"@typescript-eslint/eslint-plugin": "^8.62.0",
"@typescript-eslint/parser": "^8.62.0",
"angular-eslint": "^22.0.0",
"eslint": "^9.32.0",
"prettier": "^3.6.2",
"typescript": "^6.0.3"
}
<aside> 💡
This block is the zoneless cloud-app-starter (no zone.js). If your app is still Zone-based (kept the Eager stamps, no signals/zoneless migration), also keep "zone.js": "~0.15.1".
</aside>