Upgrade Node.js version of the sveltekit project to deploy in Vercel

You get an email from your hosting provider, Vercel. It’s a routine notice: "Upgrade your Node.js version." It seems simple enough. But what follows is a cascade of cryptic error messages, dependency conflicts, and a project that works perfectly on your machine but fails spectacularly in the cloud.
This isn't a unique situation. It's a classic software development problem known as "Dependency Hell." We just navigated it, and this post breaks down what happened, why it happened, and how we escaped.
It all started with a clear goal: upgrade a SvelteKit project to use Node.js 22 on Vercel. The first step was updating our package.json
.
"engines": {
"node": "22.x"
}
This tells Vercel which Node.js version to use. But this one line was just the tip of the iceberg.
The first deployment failed. The error was from SvelteKit's build system, specifically @sveltejs/adapter-auto
.
Error: Unsupported Node.js version... Please use Node 16 or Node 18.
This was our first major clue. The issue wasn't Vercel; it was the SvelteKit adapter. The version we had didn't recognize "Node.js 22" as a valid runtime. The fix was to explicitly use @sveltejs/adapter-vercel
and tell it which runtime to use in svelte.config.js
.
When we tried to install the new Vercel adapter, npm
threw an ERESOLVE
error. This is npm
's way of saying, "I can't do what you're asking because it creates a contradiction."
Conflict #1: SvelteKit Version
The new Vercel adapter required a newer version of SvelteKit (^2.0.0
) than our project had (^1.5.0
).
Conflict #2: Svelte Version Upgrading SvelteKit then revealed the next conflict. The new SvelteKit required Svelte 4 or 5, but our project was on Svelte 3.
Conflict #3: Vite Version Upgrading SvelteKit and Svelte then created another conflict. These new versions required Vite 5, but our project was on Vite 4.
Each time we tried to fix one dependency, another would break. We were trying to solve the puzzle one piece at a time, but the pieces were all interconnected.
The core of the problem was our package-lock.json
file. This file "locks" the exact versions of all your dependencies and sub-dependencies. It’s designed to ensure you and your teammates install the exact same package versions.
However, after our first few failed npm install
attempts, this file and the node_modules
folder became a messy, inconsistent state. npm
was trying to resolve new packages against a backdrop of old, conflicting ones. It was trying to build a new puzzle using pieces from an old, different one.
The final, successful strategy was to stop fixing and start fresh.
1. Deletion: We completely deleted the node_modules
folder and the package-lock.json
file. This removed the entire history of conflicts and gave npm
a clean slate.
2. A Correct package.json
: We manually edited the package.json
file, providing a complete list of devDependencies
that were all known to be compatible with each other (Svelte 4, SvelteKit 2, Vite 5, etc.).
package.json
{
"name": "ecom-analytics",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"test": "playwright test",
"test:unit": "vitest",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"devDependencies": {
"@playwright/test": "^1.28.1",
"@sveltejs/adapter-auto": "^2.0.0",
"@sveltejs/kit": "^1.5.0",
"autoprefixer": "^10.4.14",
"eslint": "^8.28.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-svelte": "^2.26.0",
"postcss": "^8.4.23",
"postcss-load-config": "^4.0.1",
"prettier": "^2.8.0",
"prettier-plugin-svelte": "^2.8.1",
"svelte": "^3.54.0",
"svelte-calendar": "^3.1.6",
"svelte-preprocess": "^5.0.3",
"tailwindcss": "^3.3.1",
"vite": "^4.3.0",
"vitest": "^0.25.3"
},
"type": "module",
"dependencies": {
"daisyui": "^2.51.6",
"dotenv": "^16.0.3"
}
}
package.json
{
"name": "ecom-analytics",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"test": "playwright test",
"test:unit": "vitest",
"lint": "prettier --plugin-search-dir . --check . && eslint .",
"format": "prettier --plugin-search-dir . --write ."
},
"engines": {
"node": "22.x"
},
"devDependencies": {
"@playwright/test": "^1.44.1",
"@sveltejs/adapter-vercel": "^5.3.0",
"@sveltejs/kit": "^2.5.10",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.39.0",
"postcss": "^8.4.38",
"postcss-load-config": "^5.0.3",
"prettier": "^3.2.5",
"prettier-plugin-svelte": "^3.2.3",
"svelte": "^4.2.17",
"svelte-preprocess": "^5.1.4",
"tailwindcss": "^3.4.3",
"vite": "^5.2.11",
"vitest": "^1.6.0"
},
"type": "module",
"dependencies": {
"daisyui": "^2.51.6",
"dotenv": "^16.0.3",
"svelte-calendar": "^3.1.6"
}
}
3. The Correct svelte.config.js
: With the right packages ready to be installed, we configured SvelteKit to use the Vercel adapter and specify the Node.js 22 runtime. This file is crucial for the build process.
// svelte.config.js
import preprocess from "svelte-preprocess";
import adapter from "@sveltejs/adapter-vercel";
/** @type {import('@sveltejs/kit').Config} */
const config = {
kit: {
adapter: adapter({
runtime: "nodejs22.x",
}),
},
preprocess: [
preprocess({
postcss: true,
}),
],
};
export default config;
4. Fresh Install: With a clean folder and valid configuration files, a simple npm install
worked perfectly. npm
was able to build a brand new, consistent package-lock.json
from scratch without any conflicts.
ERESOLVE
means stop and think. Don't use --force
. It means there's a real contradiction in your dependencies. node_modules
and package-lock.json
is often the fastest way out. What started as a simple version bump became a deep dive into project dependencies. By understanding the root cause, we can now approach future upgrades with a clearer strategy.