Node.js 26で標準搭載されたTemporal APIとMap.getOrInsert・Iterator.concatの使い方を、v26.8.2で実行した結果とともにまとめます。LTS昇格や削除されたAPIの確認点も分かります。
この記事が解決する悩み
- Temporal APIを実務で使えるレベルなのか知りたい
- LTSに上がる前に何を確認しておくべきか知りたい
対象読者: Node.jsでサーバーやツールを書いている人
前提知識: Node.js 22以降を使っていること
結論
Node.js 26は2026年5月5日に出たCurrent版で、2026年10月にLTSへ昇格します。いちばん大きな変化はTemporal APIがフラグなしで使えるようになったことで、日付とタイムゾーンの扱いがかなり楽になりますよ。
まず動かして確かめます。手元のNode.js 26.8.2で実行した結果がこちらです。
// node26-check.mjs
console.log('node', process.version, 'v8', process.versions.v8);
const today = Temporal.Now.plainDateISO('Asia/Tokyo');
const closing = today.with({ day: today.daysInMonth });
console.log('today :', today.toString());
console.log('closing:', closing.toString());
console.log('rest :', today.until(closing).toString());
const map = new Map();
map.getOrInsertComputed('cache-key', () => 'first call only');
console.log('upsert :', map.getOrInsertComputed('cache-key', () => 'never used'));
console.log('concat :', [...Iterator.concat([1, 2], new Set(['x']))].join(','));
実行結果です。
$ node --version
v26.8.2
$ node node26-check.mjs
node v26.8.2 v8 14.6.202.34-node.28
today : 2026-09-15
closing: 2026-09-30
rest : P15D
upsert : first call only
concat : 1,2,x
この3つが動けば、問題ありません。あとは既存コードで消されたAPIを踏んでいないかの確認だけで、移行はほぼ終わります。
Node.js 26の位置づけ
リリースノートのハイライトは4点です。Temporal APIのデフォルト有効化、V8が14.6へ更新、Undiciが8系へ、そして非推奨APIの削除がまとめて入りました。
地味に重要なのがリリースサイクルの話です。Node.js 26は旧スケジュール最後のリリースになります。Node.js 27以降は年1回のリリースに変わり、番号もカレンダー年に揃い、全バージョンがLTSになる予定です。つまり偶数系がLTSという今の常識は、26でいったん終わりです。
| 項目 | Node.js 26 | 備考 |
|---|---|---|
| リリース | 2026年5月5日 | Current |
| LTS昇格 | 2026年10月 | 保守は2027年10月から |
| V8 | 14.6.202.34 | Chromium 146相当 |
| Undici | 8.x | fetch()の中身 |
| EOL | 2029年4月 | LTSは30か月 |
Temporal APIが標準で使えるようになった
これまでTemporalはフラグ付きの実験機能でした。Node.js 24.16.0で確認するとtypeof Temporalはundefinedです。Node.js 26では同じ式がobjectを返します。フラグもポリフィルも不要になったわけです。
Dateを置き換える理由
旧Dateの問題は挙動が仕様に染み付いていることです。月が0始まり、オブジェクトが可変、タイムゾーンが実行環境依存、文字列パースが曖昧。Temporalはこの4つを別々の型に切り分けました。
Temporal.PlainDate: 日付だけ。時刻もタイムゾーンも持たないTemporal.PlainDateTime: 日時だけ。タイムゾーンは持たないTemporal.ZonedDateTime: 日時とタイムゾーンをセットで持つTemporal.Instant: エポックからの絶対時刻だけTemporal.Duration: 期間
型が分かれているので、PlainDateにタイムゾーン変換を呼ぼうとすると実行前に弾かれます。旧Dateでありがちだった「実はUTCだった」が起きにくい設計ですね。
日付計算はカレンダー単位で動く
月末の扱いが旧Dateより素直です。1月31日に1か月足すと2月28日に丸まります。旧Dateで同じことをすると、月の繰り上がりを自前で処理することになりますからね。
const jan31 = Temporal.PlainDate.from('2026-01-31');
console.log(jan31.add({ months: 1 }).toString());
console.log(jan31.daysInMonth);
console.log(jan31.until('2027-01-01', { largestUnit: 'month' }).toString());
2026-02-28
31
P11M1D
untilのlargestUnitをmonthにすると月数で、dayにすると日数で返ってきます。単位を自分で決められるのが便利ですよ。
タイムゾーンとサマータイム
2026年3月8日のアメリカ東部はサマータイム開始日で、現地時間の午前2時が存在しません。旧Dateだとこの計算を自分で補正する羽目になります。
const before = Temporal.PlainDateTime
.from('2026-03-08T01:30')
.toZonedDateTime('America/New_York');
const after = before.add({ hours: 1 });
console.log(before.toString());
console.log(after.toString());
console.log(before.until(after, { largestUnit: 'hour' }).toString());
2026-03-08T01:30:00-05:00[America/New_York]
2026-03-08T03:30:00-04:00[America/New_York]
PT1H
1時間足したのに表示は2時間進んでいます。オフセットが-05:00から-04:00に変わったので、実時間はきっちり1時間です。この挙動が標準で手に入るのは大きいですね。
V8 14.6で入った新メソッド
V8の更新に合わせて、TC39の2つのプロポーザルがそのまま使えるようになりました。
Map.getOrInsert / getOrInsertComputed
「無ければ作って入れる」を1回のメソッド呼び出しで書けます。集計コードの定番パターンが2行減りますよ。
const rows = [
{ path: '/api/users', ms: 120 },
{ path: '/api/users', ms: 80 },
{ path: '/api/posts', ms: 300 },
];
const stats = new Map();
for (const row of rows) {
const s = stats.getOrInsertComputed(row.path, () => ({ count: 0, total: 0 }));
s.count += 1;
s.total += row.ms;
}
for (const [path, s] of stats) {
console.log(path, 'n =', s.count, 'avg =', s.total / s.count);
}
/api/users n = 2 avg = 100
/api/posts n = 1 avg = 300
getOrInsertのほうは値をその場で渡す形です。第2引数は必ず評価されるので、生成コストが高いオブジェクトにはgetOrInsertComputedを使います。実際に効くのはこの違いで、前者は毎回オブジェクトを作って捨てることになります。
const cache = new Map();
// 生成されるのは最初の1回だけ
const first = cache.getOrInsertComputed('expensive', () => ({ heavy: true }));
const second = cache.getOrInsertComputed('expensive', () => ({ heavy: true }));
console.log(first === second, cache.size);
true 1
WeakMapにも同名のメソッドが入っています。オブジェクトをキーにしたキャッシュをGCに任せられるので、リクエスト単位のメモ化なんかはこれで済みます。
Iterator.concat
複数のイテラブルを1本のイテレータにまとめます。配列に変換しないので、無限ジェネレータとも組み合わせられます。
function* naturals() {
let i = 0;
while (true) {
yield i;
i += 1;
}
}
const head = [...Iterator.concat(naturals()).take(5)];
console.log(head.join(','));
console.log([...Iterator.concat([1, 2], new Set(['x']), naturals()).take(4)].join(','));
0,1,2,3,4
1,2,x,0
1つ注意点があります。非同期イテラブルは渡せません。Iterator.concat(asyncGenerator)はTypeErrorになります。async側はfor awaitで回してください。
実践ユースケース
ページネーションを遅延連結する
APIをページ単位で叩くコードは、全件配列に詰め込むとメモリを無駄に食います。必要な分だけ引ける形にしておくと、一覧画面の初期表示が軽くなりますよ。
async function* fetchPages(path, pages) {
for (let page = 1; page <= pages; page += 1) {
const res = await fetch(`${path}?page=${page}`);
const json = await res.json();
yield* json.items;
}
}
let seen = 0;
for await (const item of fetchPages('/api/items', 5)) {
console.log(item.id);
seen += 1;
if (seen >= 10) {
break;
}
}
上の例は非同期なのでfor awaitです。同期のジェネレータならIterator.concat(pageA(), pageB(), pageC())でつなげます。ストリームの前段で複数ソースを1本にまとめたいときに向いていますね。
予約時刻を複数タイムゾーンで表示する
海外チームと日程を調整するときのやつです。1つのZonedDateTimeから必要なタイムゾーンだけ取り出せます。
const booking = Temporal.ZonedDateTime.from({
timeZone: 'Asia/Tokyo',
year: 2026, month: 9, day: 18, hour: 10, minute: 30,
});
console.log(booking.toString());
console.log(booking.withTimeZone('UTC').toString());
console.log(booking.withTimeZone('America/New_York').toString());
console.log(booking.toLocaleString('ja-JP', { dateStyle: 'full', timeStyle: 'short' }));
2026-09-18T10:30:00+09:00[Asia/Tokyo]
2026-09-18T01:30:00+00:00[UTC]
2026-09-17T21:30:00-04:00[America/New_York]
2026/9/18金曜日 10:30
toLocaleStringがIntlのフォーマットをそのまま使えるのも助かります。旧Dateと行き来したいときはエポックミリ秒を経由します。Temporal.InstantにtoDate()は無いので、そこだけは気をつけてください。
const legacy = new Date(booking.epochMilliseconds);
console.log(legacy.toISOString());
const back = Temporal.Instant
.fromEpochMilliseconds(legacy.getTime())
.toZonedDateTimeISO('Asia/Tokyo');
console.log(back.toString());
2026-09-18T01:30:00.000Z
2026-09-18T10:30:00+09:00[Asia/Tokyo]
署名に文脈を埋め込む
Node.js 26のcryptoには2つ入りました。Ed25519などの生鍵フォーマット対応と、署名にcontextを渡すオプションです。生鍵はraw-publicとraw-privateで32バイトのまま扱えます。
import { generateKeyPairSync, createPrivateKey, createPublicKey, sign, verify } from 'node:crypto';
const { publicKey, privateKey } = generateKeyPairSync('ed25519');
const rawPub = publicKey.export({ format: 'raw-public' });
const rawPriv = privateKey.export({ format: 'raw-private' });
console.log(rawPub.length, rawPriv.length);
const pub = createPublicKey({ key: rawPub, format: 'raw-public', asymmetricKeyType: 'ed25519' });
const priv = createPrivateKey({ key: rawPriv, format: 'raw-private', asymmetricKeyType: 'ed25519' });
const msg = Buffer.from('hello node 26');
const ctx = Buffer.from('engineer-notes:v1');
const sig = sign(null, msg, { key: priv, context: ctx });
console.log('ok :', verify(null, msg, { key: pub, context: ctx }, sig));
console.log('wrong ctx :', verify(null, msg, { key: pub, context: Buffer.from('v2') }, sig));
32 32
ok : true
wrong ctx : false
contextはBufferかTypedArrayで渡します。文字列を渡すとERR_INVALID_ARG_TYPEで落ちるので、そこはハマりどころですね。同じ鍵で用途ごとに署名を分離できるので、トークン種別のドメイン分離に使えます。
Node.js 26で消えたAPI
移行でいちばん痛いのは削除されたAPIです。Node.js 26.8.2で実際に確認した結果をまとめます。
| 削除されたもの | 代替 |
|---|---|
res.writeHeader() |
res.writeHead() |
node:_stream_wrap ほか _stream_* の6モジュール |
公開のstream API |
--experimental-transform-types |
型注釈の実行時変換は不可 |
module.register() |
実行時非推奨。registerHooks()へ |
自分のコードが踏んでいないかは、これで一発です。
import http from 'node:http';
console.log('writeHeader:', typeof http.ServerResponse.prototype.writeHeader);
console.log('writeHead :', typeof http.ServerResponse.prototype.writeHead);
for (const name of ['_stream_wrap', '_stream_readable']) {
try {
await import(`node:${name}`);
console.log(name, ': loaded');
} catch (err) {
console.log(name, ':', err.code);
}
}
writeHeader: undefined
writeHead : function
_stream_wrap : ERR_UNKNOWN_BUILTIN_MODULE
_stream_readable : ERR_UNKNOWN_BUILTIN_MODULE
ログにERR_UNKNOWN_BUILTIN_MODULEが出ていたら、その依存パッケージが古い可能性が高いですね。npm lsで該当箇所を辿って、メジャー更新できるか確認してみてください。
【広告】お名前.comならドメイン取得が格安。![]()
まとめ
Node.js 26は派手な新機能は少ないですが、Temporalがフラグなしで入ったことで実務の書き方が変わります。日付計算を自前で補正していたコードは、かなり削れるはずです。
| 機能 | Node 24 LTS | Node 26 |
|---|---|---|
| Temporal API | フラグ付き | デフォルト有効 |
| Map.getOrInsert | なし | あり |
| Iterator.concat | なし | あり |
| Ed25519生鍵フォーマット | なし | あり |
| Undici | 7系 | 8系 |
| V8 | 13.6 | 14.6 |
本番投入のタイミングですが、2026年10月のLTS昇格を待つのが無難です。今すぐ試したいならバージョンマネージャで26系を入れて、まず日付まわりのテストを通してみてください。Temporalで書き直したコードは、そのまま残せますよ。
日付とタイムゾーンのバグは見つけにくいので、テストが薄い部分から順にTemporalへ寄せていくのがおすすめです。
検証環境
| OS | Windows 11 (build 10.0.26200) |
|---|---|
| CPU | AMD Ryzen 9 PRO 8945HS w/ Radeon 780M Graphics |
| メモリ | 28GB |
| 言語/ツール | Node.js 26.8.2(公式のポータブル配布を一時ディレクトリに展開) |
| 使用コマンド | node test.mjs |
| 測定日 | 2026-09-17 |
上記の環境で実際に実行して確認した結果です。環境が異なる場合は挙動が変わることがあります。

参考
本文の記述は以下の一次情報を確認して書いています。
- Node.js 公式リリースノート — 26.0.0 (Current)
- Node.js 公式 API ドキュメント
- TC39 提案 — Temporal(日時API)
- TC39 提案 — Map.prototype.upsert(getOrInsert / getOrInsertComputed)
あわせて読みたい
- 【pnpm】ERR_PNPM_IGNORED_BUILDS の直し方 — allowBuilds へ移行
- 【Node.js】ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX の直し方
- 【Vite】Vite 8 移行ガイド2026 — Rolldown 統合で壊れる設定
- 【Biome】ESLint + Prettier から Biome v2 への移行ガイド2026
【広告】このサイトはConoHa WINGで運営しています。安定した高速サーバーで快適にブログを書けています。いつもありがとう!!![]()

コメント