Node.js Error

TypeError [ERR_INVALID_URL]: Invalid URL

error code: ERR_INVALID_URL

Complete reference — thrown by new URL() when the WHATWG URL parser rejects a malformed string. Covers every cause: missing protocol, relative URL without base, undefined/null input, empty string, whitespace, all-numeric hostname, IPv6 syntax, URL-encoding issues, fetch()/axios/undici indirect throws, environment variable gotchas, and the URL.canParse() guard introduced in Node 19.

Quick Answer: 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.

Exact error strings you will encounter:
TypeError [ERR_INVALID_URL]: Invalid URL
TypeError [ERR_INVALID_URL]: Invalid URL: example.com/path
TypeError [ERR_INVALID_URL]: Invalid URL: /api/users
TypeError [ERR_INVALID_URL]: Invalid URL: undefined
TypeError [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

PropertyValueNotes
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

CauseFailing input exampleFix
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
Tip — import.meta.url as a base: In ESM modules, 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

  1. Check err.input in your catch block — it shows the exact string new URL() received. If it is 'undefined' or 'null', trace back the variable that should have been set.
  2. If the input is a bare hostname or path, add the https:// scheme or use the second-argument base pattern: new URL(path, base).
  3. For environment variables: confirm the variable is set in every deployment environment (local, staging, production, Docker, CI). Check for missing http:// or https:// prefix.
  4. Call input.trim() before new URL(input) to strip accidental whitespace from .env files or config sources.
  5. For fetch / axios errors: look at the stack trace to confirm the error originates from new URL(). If using axios, ensure baseURL is set on the instance when making relative requests.
  6. For Next.js / Vercel: check the Vercel dashboard environment variables tab. NEXTAUTH_URL must include the full scheme and hostname. Run vercel env pull to sync to your local .env.local.
  7. 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.
  8. On Node 19.9+ / 18.17+, use URL.canParse(input) to test validity without a try/catch — useful in hot paths.
  9. For all-numeric hostnames: check whether the hostname value is actually a numeric ID that was accidentally used where a hostname should go.
  10. 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.
Do not use url.parse() as a workaround: The legacy 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:

  1. Go to your Vercel project → Settings → Environment Variables
  2. Add NEXTAUTH_URL (or the relevant variable) with a full URL including https://
  3. Redeploy — environment variable changes require a new deployment
  4. Run vercel env pull .env.local locally to sync production env vars

Add startup validation in next.config.js to catch this before deployment reaches users.

Related Errors