TL;DR
不正な形式または相対URLをnew URL()、fetch()、またはその他の完全に有効な絶対URLを要求するAPIに渡しています。2秒で解決:URLにスキーム(https://またはhttp://)が含まれていることを確認してください。相対パスの場合は、第2引数としてベースURLを渡してください。
// 壊れている
const url = new URL('/api/users'); // TypeError [ERR_INVALID_URL]: Invalid URL
// 修正済み
const url = new URL('/api/users', 'https://example.com');
console.log(url.href); // https://example.com/api/users
根本原因
Node.jsはWHATWG URL標準に従っています。これはブラウザが使用するのと同じ仕様です。つまり、new URL(input, base?)は設計上厳格です。baseなしで相対パスを渡すと、即座に例外をスローし、一切の質問を受け付けません。
このエラーを確実に引き起こす状況は以下の通りです:
- ベースなしの相対パス:
new URL('/path') - URLが
undefined、null、または空文字列 - URLにスペースまたは
#や|などのエンコードされていない特殊文字が含まれている - プロトコルが欠落している:
new URL('example.com/path')——有効に見えても、そうではない - 変数補間の失敗——文字列が予期しない内容になっている
- Node.jsで
fetch('/api/data')を呼び出す——ブラウザと異なり、Node.jsには暗黙的なベースURLのコンテキストがない
修正方法
1. 相対URL——第2引数としてベースを渡す
これは最も一般的なミスです。ブラウザにはwindow.locationという暗黙的なベースがありますが、Node.jsにはありません。明示的に指定する必要があります。
// 誤り
const u = new URL('/users/123');
// 正しい
const u = new URL('/users/123', 'https://api.example.com');
console.log(u.href); // https://api.example.com/users/123
2. URLがundefinedまたはnull
環境変数の典型的な落とし穴——変数が設定されていない場合、process.env.API_URLはundefinedとなり、new URL(undefined)は即座にクラッシュします。
const endpoint = process.env.API_URL; // 設定されていない場合はundefined
new URL(endpoint); // TypeError [ERR_INVALID_URL]: Invalid URL
// 修正:使用前に検証する
if (!endpoint) throw new Error('API_URL env var is not set');
const url = new URL(endpoint);
3. プロトコルの欠落 / ホスト名のみ
api.example.com/usersは有効なURLのように見えます。しかし、明示的なスキームを必要とするWHATWGパーサーにとっては有効ではありません。
// 誤り——スキームなし
new URL('api.example.com/users');
// 正しい
new URL('https://api.example.com/users');
4. URLにスペースまたは無効な文字が含まれている
ユーザー入力や動的データから構築されたURLは、最初にエンコードする必要があります。URLSearchParamsはこれを自動的に処理します——テンプレートリテラルの代わりに使用してください。
const searchTerm = 'hello world';
// 誤り
const u = new URL(`https://example.com/search?q=${searchTerm}`);
// 正しい——URLSearchParamsが自動でエンコード
const u = new URL('https://example.com/search');
u.searchParams.set('q', searchTerm);
console.log(u.href); // https://example.com/search?q=hello+world
5. Node.jsでの相対URLを使ったfetch()
Node 18以降にはネイティブのfetch()が搭載されていますが、注意点があります:ブラウザと異なり、ベースURLがありません。Reactやブラウザでうまくいく相対パスは、ここでは失敗します。常に絶対URLを渡してください。
// Node.jsでは誤り(ブラウザでは動作するが、ここでは失敗する)
const res = await fetch('/api/data');
// 正しい
const BASE = process.env.API_URL ?? 'http://localhost:3000';
const res = await fetch(`${BASE}/api/data`);
6. ユーザー入力にはtry/catchでラップする
外部からの入力を信頼しないでください。ラップし、エラーをキャッチし、プロセスをクラッシュさせる代わりに gracefullyに失敗してください。
function parseUrl(input) {
try {
return new URL(input);
} catch {
return null; // またはより分かりやすいエラーをスロー
}
}
const result = parseUrl(userInput);
if (!result) {
console.error('Invalid URL provided:', userInput);
}
7. URLを動的に構築する——URL APIを適切に使用する
文字列の連結は脆弱です——余分なスラッシュやエンコードされていない文字が1つあるだけで、このエラーに戻ってしまいます。パスとクエリの構築はURL APIに任せてください。
const base = new URL('https://api.example.com');
const endpoint = new URL('/v2/users', base);
endpoint.searchParams.set('page', '2');
endpoint.searchParams.set('limit', '50');
console.log(endpoint.href);
// https://api.example.com/v2/users?page=2&limit=50
確認方法
これをcheck-url.jsというファイルに保存し、node check-url.jsで実行してください。すべての行にOK:が表示されるはずです。FAIL:の行は、どのケースがまだ壊れているかを正確に示しています。
const cases = [
['https://example.com/path', undefined],
['/relative', 'https://example.com'],
['https://example.com/search', undefined],
];
for (const [input, base] of cases) {
try {
const u = base ? new URL(input, base) : new URL(input);
console.log('OK:', u.href);
} catch (e) {
console.error('FAIL:', input, '->', e.message);
}
}
クイックチェックリスト
- URLは
http://またはhttps://で始まっていますか? - 変数は実際に文字列ですか——
undefinedやnullではありませんか? - 相対パスの場合:ベースURLが第2引数として渡されていますか?
- URLにエンコードされていないスペースまたは特殊文字はありませんか?
- Node.jsで
fetch()を使用している場合:URLは絶対URLですか?
参考資料
- Node.jsドキュメント: WHATWG URL API — new URL(input, base)
- MDN: URL() コンストラクター
- WHATWG URL標準: url.spec.whatwg.org

