TypeError [ERR_INVALID_URL]: Invalid URL means new URL(input) (or a library calling it internally) received a string that the WHATWG URL parser rejects. Check err.input to see the exact string. The three most common fixes: (1) add a missing https:// scheme, (2) pass an absolute base as the second argument — new URL(path, base) — for relative paths, or (3) validate the value before constructing: URL.canParse(input) (Node 19+) or a try/catch wrapper.
What is ERR_INVALID_URL?
ERR_INVALID_URL is a TypeError thrown by Node.js's implementation
of the WHATWG URL Standard whenever
new URL(input) or new URL(input, base) receives a string that does
not conform to the standard. It replaced the permissive legacy url.parse(), which
was deprecated in Node.js v11 (and later marked for removal) due to security ambiguities.
The WHATWG URL parser is substantially stricter than its predecessor. Strings that
url.parse() would silently accept — bare hostnames, relative paths, all-numeric
hostnames — cause new URL() to throw immediately.
The error object has two notable properties beyond the standard message and
code: err.input holds the exact string that failed to parse, and
err.code is always 'ERR_INVALID_URL'. Reading err.input
in your catch block immediately reveals whether the value was undefined, an empty
string, or missing a scheme.
TypeError [ERR_INVALID_URL]: Invalid URLTypeError [ERR_INVALID_URL]: Invalid URL: example.com/pathTypeError [ERR_INVALID_URL]: Invalid URL: /api/usersTypeError [ERR_INVALID_URL]: Invalid URL: undefinedTypeError [ERR_INVALID_URL]: Invalid URL: (empty string)TypeError: Failed to parse URL from /relative/path (undici / Node built-in fetch)TypeError [ERR_INVALID_URL]: Invalid URL: http:// (scheme present but no host)
Full Error Example
// Triggering code
const { URL } = require('url'); // or globalThis.URL in modern Node.js
const endpoint = new URL(process.env.API_URL); // API_URL is undefined
// Stack trace
TypeError [ERR_INVALID_URL]: Invalid URL
at new URL (node:internal/url:775:36)
at Object.<anonymous> (/app/src/client.js:5:20)
at Module._compile (node:internal/modules/cjs/loader:1364:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1422:10)
at Module.load (node:internal/modules/cjs/loader:1203:32)
at Function.Module._load (node:internal/modules/cjs/loader:1019:12) {
input: 'undefined', // ← the string that was parsed: process.env returned undefined,
code: 'ERR_INVALID_URL' // coerced to the literal string 'undefined'
}
Error Object Properties
| Property | Value | Notes |
|---|---|---|
err.name |
'TypeError' |
Always a TypeError subclass |
err.code |
'ERR_INVALID_URL' |
Node.js error code — use for instanceof-style checks |
err.input |
The string passed to new URL() |
Log this first — it tells you exactly what was wrong |
err.message |
'Invalid URL' or 'Invalid URL: <input>' |
Varies slightly across Node.js versions |
All Causes at a Glance
| Cause | Failing input example | Fix |
|---|---|---|
| Missing protocol scheme | 'example.com/api' |
Add https://: 'https://example.com/api' |
| Relative URL without base | '/api/users' |
new URL('/api/users', 'https://example.com') |
undefined env var coerced to string |
'undefined' |
Validate env vars at startup; throw early if missing |
| Empty string | '' |
Guard with if (!input) before calling new URL() |
| Whitespace in URL string | ' https://example.com ' |
Call input.trim() before parsing |
| Scheme present but host missing | 'http://' |
Validate that the env var or config value is complete |
| All-numeric hostname | 'http://810492618439' |
WHATWG parser treats all-digit hosts as IPv4 — value overflows; ensure real hostname |
| Malformed IPv6 literal | 'http://[::1' (unclosed bracket) |
Use correct IPv6 syntax: 'http://[::1]:3000' |
| Unencoded special characters that invalidate the URL | 'https://example.com/path with spaces' |
Encode with encodeURIComponent() on path segments |
| Double colon / garbled scheme | 'http:://example.com' |
Fix the scheme separator: one colon, two slashes |
axios relative path without baseURL |
axios.get('/users') |
axios.create({ baseURL: 'https://api.example.com' }) |
fetch() / undici with invalid URL |
fetch(process.env.URL) where env is unset |
Validate the variable before calling fetch() |
| Punycode / IDN domain edge case | 'http://xn--www-4m0aa.hergivenhair.com/?y=...' |
Verify Punycode encoding is correct; some IDNs rejected by WHATWG parser differ from browsers |
Cause 1 – Missing Protocol Scheme
The single most common cause. new URL('example.com/path') fails because the WHATWG
URL parser requires an absolute URL with a scheme. url.parse('example.com/path')
used to return an object with pathname: 'example.com/path'; the new parser throws.
// ❌ Missing scheme — throws ERR_INVALID_URL
new URL('example.com/api/v1');
new URL('example.com');
new URL('//example.com/path'); // protocol-relative also fails without a base
// ✅ Absolute URL with scheme
new URL('https://example.com/api/v1');
new URL('http://localhost:3000/health');
// ✅ Fix environment variable that holds only a host
const host = process.env.API_HOST; // 'api.example.com'
const endpoint = new URL('https://' + host + '/v1/users');
// ✅ ESM equivalent — same API
import { URL } from 'node:url';
const u = new URL('https://api.example.com/v1/users');
Cause 2 – Relative URL Without a Base
new URL('/api/users') throws because a path-only string is not a valid absolute URL.
The fix is to provide an absolute base URL as the second argument. The constructor resolves the
relative reference against the base, exactly as a browser would.
// ❌ Relative path — no base provided
new URL('/api/users'); // ERR_INVALID_URL
new URL('./config'); // ERR_INVALID_URL
new URL('api/users'); // ERR_INVALID_URL
// ✅ Provide an absolute base as the second argument
new URL('/api/users', 'https://api.example.com');
// → https://api.example.com/api/users
new URL('./config', 'https://api.example.com/v1/');
// → https://api.example.com/v1/config
// ✅ Derive base from a validated env var
const BASE = new URL(process.env.API_BASE_URL ?? 'https://api.example.com');
const endpoint = new URL('/users', BASE);
// ✅ ESM — import.meta.url as a base for local file paths
import { fileURLToPath } from 'node:url';
const configURL = new URL('./config.json', import.meta.url);
const configPath = fileURLToPath(configURL); // → absolute file path
import.meta.url
is the file:// URL of the current module. You can use it as a base to resolve
sibling files: new URL('./data.json', import.meta.url). This is the idiomatic ESM
replacement for path.join(__dirname, 'data.json') in CommonJS.
Cause 3 – undefined or null Input (Environment Variables)
When process.env.SOME_VAR is not set, it returns undefined.
JavaScript coerces undefined to the string 'undefined' during
string concatenation or when passed directly to new URL(). The parser then tries
to parse the literal string 'undefined' and throws. This is the dominant cause
of the error appearing in production but not locally (where the .env file is
present).
// Environment: API_URL is not set in this deployment
// ❌ process.env.API_URL is undefined — coerced to the string 'undefined'
const url = new URL(process.env.API_URL);
// TypeError [ERR_INVALID_URL]: Invalid URL { input: 'undefined' }
// ❌ Same trap with null
const url2 = new URL(null);
// TypeError [ERR_INVALID_URL]: Invalid URL { input: 'null' }
// ✅ Validate at startup — fail fast with a clear message
function requireEnvUrl(name) {
const value = process.env[name];
if (!value) {
throw new Error(
`Missing required environment variable: ${name}\n` +
`Set it to a full URL including scheme, e.g. https://api.example.com`
);
}
// Also validate it is actually a parseable URL
try {
return new URL(value);
} catch {
throw new Error(
`Environment variable ${name} is not a valid URL: "${value}"\n` +
`Ensure it includes the scheme (https://) and a valid hostname.`
);
}
}
const API_BASE = requireEnvUrl('API_URL'); // throws at startup, not mid-request
// ✅ Nullish coalescing fallback (only if a default makes sense)
const endpoint = new URL(process.env.API_URL ?? 'https://api.example.com');
Cause 4 – Empty String or Whitespace
An empty string or a string containing only whitespace is not a valid URL. Config files, form
inputs, or database values may hold empty strings when not configured. Leading or trailing
whitespace (newlines from .env files, spaces from copy-paste) causes the same error.
// ❌ Empty string
new URL(''); // ERR_INVALID_URL { input: '' }
new URL(' '); // ERR_INVALID_URL { input: ' ' }
// ❌ .env file value with accidental newline:
// API_URL=https://api.example.com\n ← trailing newline
new URL('https://api.example.com\n'); // ERR_INVALID_URL
// ✅ Always trim before parsing
function parseUrl(raw) {
if (typeof raw !== 'string' || raw.trim() === '') {
throw new TypeError(`Expected a non-empty URL string, got: ${JSON.stringify(raw)}`);
}
return new URL(raw.trim());
}
parseUrl(process.env.API_URL); // trims whitespace and newlines
parseUrl(userInput.trim()); // for user-supplied values
Cause 5 – fetch(), undici, and axios Throwing Internally
Node.js 18+ built-in fetch() is powered by undici, which calls new URL()
internally. When you pass an invalid URL to fetch(), the ERR_INVALID_URL
error originates inside undici's URL resolution and surfaces from your fetch() call.
The stack trace will show node:internal/deps/undici/undici.
axios on Node.js uses the same WHATWG URL constructor in its adapter since v1.x. Passing a
relative path like axios.get('/users') without a baseURL configured
causes ERR_INVALID_URL.
// ── fetch() (Node 18+ built-in / undici) ──────────────────────────────────
// ❌ Relative URL — fetch() cannot resolve it without a base
await fetch('/api/health');
// TypeError [ERR_INVALID_URL]: Invalid URL
// ❌ Undefined env var coerced to 'undefined'
await fetch(process.env.SERVICE_URL + '/health');
// TypeError [ERR_INVALID_URL]: Invalid URL: undefined/health
// ✅ Always use an absolute URL
await fetch('https://api.example.com/health');
// ✅ Compose from a validated base
const BASE_URL = process.env.SERVICE_URL?.trimEnd().replace(/\/$/, '')
?? (() => { throw new Error('SERVICE_URL is not set'); })();
await fetch(`${BASE_URL}/health`);
// ── axios ──────────────────────────────────────────────────────────────────
const axios = require('axios');
// ❌ Relative path without a baseURL configured on the instance
axios.get('/users');
// TypeError [ERR_INVALID_URL]: Invalid URL
// ✅ Create an instance with baseURL
const api = axios.create({
baseURL: process.env.API_BASE_URL ?? 'https://api.example.com',
timeout: 5000,
});
await api.get('/users'); // resolves to https://api.example.com/users
// ── undici (direct) ────────────────────────────────────────────────────────
import { request } from 'undici';
// ❌ Invalid URL
await request('example.com/api'); // no scheme → ERR_INVALID_URL
// ✅ Full URL required
await request('https://example.com/api');
Cause 6 – All-Numeric Hostname
The WHATWG URL parser treats an all-numeric hostname as an IPv4 address. If the numeric value
overflows a valid IPv4 range (0–4294967295), the URL is rejected. Legacy tools like
url.parse(), curl, and ping may accept the same hostname
without error, making this a subtle production-only failure.
// ❌ All-numeric hostname exceeds IPv4 range — WHATWG rejects it
new URL('http://810492618439');
// TypeError [ERR_INVALID_URL]: Invalid URL
// ❌ Also fails — the WHATWG parser interprets '999999' as the number 999999
// which is a valid IPv4 octet overflow
new URL('http://999999:8080/path');
// url.parse() (legacy) would silently accept these — that leniency was intentional
// but created host-confusion security issues
// ✅ Use a real hostname or valid IP
new URL('http://api.internal:8080/path');
new URL('http://192.168.1.10:8080/path');
// ✅ Diagnostic: check whether a hostname is all-numeric
function isAllNumericHost(hostname) {
return /^\d+$/.test(hostname);
}
const host = extractHostFromConfig(); // e.g. '810492618439'
if (isAllNumericHost(host)) {
throw new Error(`Config host "${host}" is all-numeric and rejected by the WHATWG URL parser.`);
}
Cause 7 – Malformed IPv6 Literal
IPv6 addresses in URLs must be wrapped in square brackets with correct syntax. Common mistakes include unclosed brackets, missing brackets entirely, or using a colon separator instead of the bracket form.
// ❌ IPv6 without brackets
new URL('http://::1:3000/api'); // ERR_INVALID_URL
new URL('http://2001:db8::1/api'); // ERR_INVALID_URL
// ❌ Unclosed bracket
new URL('http://[::1:3000/api'); // ERR_INVALID_URL
// ✅ Correct IPv6 URL syntax
new URL('http://[::1]:3000/api');
new URL('https://[2001:db8::1]/api');
new URL('http://[::1]/'); // port is optional
Cause 8 – URL-Encoding and Special Characters
Spaces and certain special characters in path segments or query strings cause the URL parser to
fail when they appear in positions where only percent-encoded characters are valid. Use
encodeURIComponent() on user-supplied path segments and query parameter values.
Do not encode the entire URL — only the dynamic parts.
// ❌ Space in path — ERR_INVALID_URL in strict parsing contexts
// Note: new URL() actually accepts spaces in paths in many Node.js versions,
// but other characters (e.g. raw [ ] in query strings) cause failures
new URL('https://example.com/search?q=hello world'); // accepted — URL encodes space
new URL('https://example.com/path[0]'); // may throw depending on Node version
// ✅ Encode dynamic segments properly
const query = 'node.js error [ERR_INVALID_URL]';
const searchUrl = new URL('https://example.com/search');
searchUrl.searchParams.set('q', query); // URLSearchParams handles encoding automatically
console.log(searchUrl.href);
// → https://example.com/search?q=node.js+error+%5BERR_INVALID_URL%5D
// ✅ Encode path segments individually
const username = 'john doe/admin';
const profileUrl = new URL(
`/users/${encodeURIComponent(username)}/profile`,
'https://api.example.com'
);
// ✅ Use URL object properties to build URLs safely — avoids manual string concatenation
const u = new URL('https://api.example.com/v2/search');
u.searchParams.append('filter', 'status=active&type=user');
u.searchParams.append('page', '1');
console.log(u.toString()); // properly encoded
Cause 9 – Next.js and SSR Framework Gotchas
In Next.js API routes, Server Actions, and middleware, new URL() is called
internally by the framework on request URLs, NEXTAUTH_URL, and other configuration
values. Missing environment variables in Vercel/Netlify deployments are a very common trigger.
// next.config.js — ensure env vars are present before the build/start
// ERR_INVALID_URL in Next.js middleware often means NEXTAUTH_URL is unset
// ── Next.js API route ──────────────────────────────────────────────────────
// ❌ process.env.NEXTAUTH_URL not set in production → 'undefined'
export default function handler(req, res) {
const callbackUrl = new URL('/api/auth/callback', process.env.NEXTAUTH_URL);
// TypeError [ERR_INVALID_URL]: Invalid URL { input: 'undefined' }
}
// ✅ Validate at startup in next.config.js or a dedicated env check file
// env-check.js (imported at the top of next.config.js)
const REQUIRED_URLS = ['NEXTAUTH_URL', 'NEXT_PUBLIC_API_URL'];
for (const key of REQUIRED_URLS) {
if (!process.env[key]) {
throw new Error(`Missing env var: ${key} — set it in .env.local or Vercel dashboard`);
}
try { new URL(process.env[key]); } catch {
throw new Error(`${key}="${process.env[key]}" is not a valid URL — include https://`);
}
}
// ── Next.js App Router — Server Component ──────────────────────────────────
// Use the request URL (already absolute) as the base for relative resolution
import { headers } from 'next/headers';
export default async function Page() {
const headersList = headers();
const host = headersList.get('host');
const proto = process.env.NODE_ENV === 'production' ? 'https' : 'http';
const baseUrl = `${proto}://${host}`;
const apiUrl = new URL('/api/data', baseUrl); // safe — base is always valid
const data = await fetch(apiUrl.toString()).then(r => r.json());
return <pre>{JSON.stringify(data)}</pre>;
}
Safe URL Parsing Patterns
Pattern A — URL.canParse() guard (Node 19.9.0 / 18.17.0+)
// URL.canParse() returns true/false — no try/catch needed
// Available from Node.js 19.9.0 and backported to 18.17.0
// Check absolute URL
if (URL.canParse(userInput)) {
const u = new URL(userInput);
console.log(u.hostname);
} else {
console.error('Invalid URL:', userInput);
}
// Check relative URL against a base
if (URL.canParse(path, 'https://api.example.com')) {
const u = new URL(path, 'https://api.example.com');
await fetch(u.toString());
} else {
throw new TypeError(`Cannot resolve path "${path}" against base URL`);
}
// ESM
import { URL } from 'node:url';
const valid = URL.canParse(process.env.WEBHOOK_URL ?? '');
if (!valid) throw new Error('WEBHOOK_URL is not a valid URL');
Pattern B — try/catch wrapper (all Node.js versions)
// CJS
function parseUrlSafe(input, base) {
try {
return base ? new URL(input, base) : new URL(input);
} catch (err) {
if (err.code === 'ERR_INVALID_URL') {
return null; // or re-throw with more context
}
throw err; // unexpected error — rethrow
}
}
const url = parseUrlSafe(process.env.API_URL);
if (!url) {
console.error('API_URL is not set or invalid');
process.exit(1);
}
// ESM async factory — validate all URLs at module load time
// config.mjs
function requireUrl(envKey, fallback) {
const raw = (process.env[envKey] ?? fallback ?? '').trim();
try {
return new URL(raw);
} catch {
throw new Error(
`${envKey}="${raw}" is not a valid URL.\n` +
`Check that it includes a scheme (https://) and a valid hostname.`
);
}
}
export const API_BASE = requireUrl('API_BASE_URL', 'https://api.example.com');
export const AUTH_BASE = requireUrl('AUTH_URL'); // throws if unset or invalid
Pattern C — Safe URL builder for dynamic segments
// Build URLs from parts without manual string concatenation
// This avoids accidental protocol injection or path traversal
function buildApiUrl(base, path, params = {}) {
// Validate base at call time (or use a module-level constant)
const url = new URL(path, base);
for (const [key, value] of Object.entries(params)) {
if (value !== undefined && value !== null) {
url.searchParams.set(key, String(value));
}
}
return url;
}
const base = new URL('https://api.example.com');
// Works correctly for all inputs:
buildApiUrl(base, '/v1/users', { page: 1, limit: 20 });
buildApiUrl(base, '/v1/search', { q: 'node [ERR_INVALID_URL]', lang: 'en' });
// With path segments — use encodeURIComponent for untrusted segments
const userId = '42/admin'; // adversarial segment
const userUrl = new URL(`/v1/users/${encodeURIComponent(userId)}`, base);
Pattern D — Detecting the error code reliably
// Always check err.code rather than parsing err.message (messages change across versions)
async function fetchWithValidation(rawUrl) {
let url;
try {
url = new URL(rawUrl);
} catch (err) {
if (err.code === 'ERR_INVALID_URL') {
throw new TypeError(
`fetchWithValidation received an invalid URL: "${err.input}"\n` +
`Ensure the URL includes a scheme (https://) and a valid hostname.`
);
}
throw err;
}
const res = await fetch(url);
return res.json();
}
Debugging Checklist
- Check
err.inputin your catch block — it shows the exact stringnew URL()received. If it is'undefined'or'null', trace back the variable that should have been set. - If the input is a bare hostname or path, add the
https://scheme or use the second-argument base pattern:new URL(path, base). - For environment variables: confirm the variable is set in every deployment environment (local, staging, production, Docker, CI). Check for missing
http://orhttps://prefix. - Call
input.trim()beforenew URL(input)to strip accidental whitespace from.envfiles or config sources. - For fetch / axios errors: look at the stack trace to confirm the error originates from
new URL(). If using axios, ensurebaseURLis set on the instance when making relative requests. - For Next.js / Vercel: check the Vercel dashboard environment variables tab.
NEXTAUTH_URLmust include the full scheme and hostname. Runvercel env pullto sync to your local.env.local. - Add startup validation: call
new URL(process.env.REQUIRED_URL)at application boot and let it throw before accepting any traffic — this turns a mid-request crash into a clear startup failure. - On Node 19.9+ / 18.17+, use
URL.canParse(input)to test validity without a try/catch — useful in hot paths. - For all-numeric hostnames: check whether the hostname value is actually a numeric ID that was accidentally used where a hostname should go.
- If the error appears in a library stack trace (undici, node-fetch, got, superagent): the library is calling
new URL()on the URL you passed — the fix is in your code, not the library.
url.parse()
is deprecated (Node.js 11+) and slated for removal. It was deprecated precisely because its
leniency enabled host-confusion attacks. The correct fix is to pass well-formed absolute URLs
to new URL(), not to fall back to the permissive legacy parser.
Frequently Asked Questions
What is TypeError [ERR_INVALID_URL]: Invalid URL in Node.js?
ERR_INVALID_URL is a TypeError thrown by Node.js whenever new URL(input) or new URL(input, base) receives a string that fails the WHATWG URL parser. The thrown error has a code of 'ERR_INVALID_URL' and an input property holding the exact string that was rejected. The WHATWG parser is stricter than the legacy url.parse() — it rejects relative URLs with no base, strings missing a protocol scheme, empty strings, undefined coerced to the string 'undefined', whitespace, and all-numeric hostnames.
How do I fix ERR_INVALID_URL caused by a missing protocol / scheme?
Add the scheme. new URL('example.com/path') throws; new URL('https://example.com/path') succeeds. For environment variables that contain only a hostname, prepend the scheme:
// ❌ Missing scheme
new URL(process.env.API_HOST); // 'api.example.com' → ERR_INVALID_URL
// ✅ Prepend scheme
new URL('https://' + process.env.API_HOST);
Or better — store the full URL including scheme in the environment variable and validate at startup.
How do I pass a relative URL to new URL() without getting ERR_INVALID_URL?
Use the two-argument form: new URL(relativePathOrRef, absoluteBase). The second argument must be a valid absolute URL string or a URL object.
new URL('/api/users', 'https://api.example.com');
// → https://api.example.com/api/users
new URL('../config', 'https://api.example.com/v2/');
// → https://api.example.com/config
// In ESM — use import.meta.url as the base for sibling files
const dataUrl = new URL('./data.json', import.meta.url);
Why does fetch() throw TypeError [ERR_INVALID_URL]: Invalid URL?
Node.js's built-in fetch() (backed by undici) calls new URL() internally on the URL argument. If that string is invalid — a relative path, an undefined environment variable coerced to 'undefined', or an empty string — new URL() throws ERR_INVALID_URL and it surfaces from your fetch() call. The fix is the same as for any direct new URL() call: pass a valid absolute URL string.
Why does axios throw TypeError [ERR_INVALID_URL]: Invalid URL?
Axios throws ERR_INVALID_URL when you call axios.get('/relative/path') without a baseURL configured on the instance. Axios's Node.js adapter uses the WHATWG URL constructor to resolve the full request URL. A bare relative path is not a valid URL. Fix by creating an axios instance with a baseURL:
const api = axios.create({
baseURL: process.env.API_BASE_URL ?? 'https://api.example.com',
});
await api.get('/users'); // resolves correctly
How do I use URL.canParse() to avoid ERR_INVALID_URL?
URL.canParse(input) was added in Node.js 19.9.0 and backported to 18.17.0. It returns true if new URL(input) would succeed, false otherwise — no try/catch needed.
// Single-argument form
if (URL.canParse(userInput)) {
const u = new URL(userInput);
} else {
return res.status(400).json({ error: 'Invalid URL' });
}
// Two-argument form — checks relative reference against base
if (URL.canParse(path, 'https://api.example.com')) {
const resolved = new URL(path, 'https://api.example.com');
}
For Node.js versions before 18.17.0, use a try/catch wrapper function instead.
Why does ERR_INVALID_URL happen in production but not locally?
Almost always an environment variable that exists in your local .env file but is not set in the production environment (Vercel, Heroku, Docker, Kubernetes, GitHub Actions). When the variable is missing, process.env.MY_URL returns undefined, which JavaScript coerces to the string 'undefined' — an invalid URL. Fix by adding the variable to your deployment platform's environment configuration and validating all URL env vars at application startup.
What is the difference between ERR_INVALID_URL and url.parse() behavior?
The legacy require('url').parse() was deliberately lenient — it parsed bare hostnames, relative paths, all-numeric hostnames, and protocol-relative strings without throwing. It was deprecated in Node.js v11 because this leniency created host-confusion security vulnerabilities. The WHATWG URL constructor (new URL()) follows the WHATWG URL Standard strictly and throws ERR_INVALID_URL for inputs the legacy parser would accept. The correct fix is not to fall back to url.parse() but to pass well-formed absolute URLs.
How do I fix ERR_INVALID_URL in Next.js / Vercel?
The most common cause in Next.js is NEXTAUTH_URL or another URL environment variable missing from the Vercel/Netlify deployment. Steps to fix:
- Go to your Vercel project → Settings → Environment Variables
- Add
NEXTAUTH_URL(or the relevant variable) with a full URL includinghttps:// - Redeploy — environment variable changes require a new deployment
- Run
vercel env pull .env.locallocally to sync production env vars
Add startup validation in next.config.js to catch this before deployment reaches users.
Related Errors
TypeError [ERR_INVALID_ARG_TYPE]— thrown when a Node.js built-in receives the wrong type; often co-occurs when anundefinedenv var reaches a function expecting a string before any URL parsing happensENOTFOUND: getaddrinfo ENOTFOUND— thrown after a URL is parsed successfully but DNS resolution fails; if you see ENOTFOUND after fixing ERR_INVALID_URL, the hostname is now valid but unreachableECONNREFUSED: connection refused— the next error you may see after fixing the URL: the URL is valid but nothing is listening on that host/portTypeError: Cannot read properties of undefined— same root cause (undefined env var or missing config value) manifesting one step earlier when a property is accessed before the URL is constructedUnhandledPromiseRejection— ifERR_INVALID_URLis thrown inside an async function without a try/catch, it surfaces as an unhandled promise rejectionUNABLE_TO_VERIFY_LEAF_SIGNATURE— a TLS error that can follow once the URL is fixed and an HTTPS connection is attempted