
The Joke Domain That Outlived Gatsby
Five years ago I bought a domain about a coworker's Dr Pepper habit. Here is what building it in Gatsby, then rebuilding it on Next.js 16, taught me about side projects that outlive their framework.
Frontend and Node patterns that survive contact with real users, real bugs, and real deadlines.


Five years ago I bought a domain about a coworker's Dr Pepper habit. Here is what building it in Gatsby, then rebuilding it on Next.js 16, taught me about side projects that outlive their framework.

Every reliable way to update Node.js on Linux, macOS, and Windows. Covers nvm, fnm, Volta, n, the nodejs.org installer, apt/brew/winget, Docker, GitHub Actions, per-project pinning, and the rebuild-native-modules step everyone forgets.

globalThis.crypto (the Web Crypto API) is a global in Node 19+ with no shim or flag: crypto.randomUUID(), getRandomValues(), and crypto.subtle. How it differs from the node:crypto module, and when you still need a shim.

Add a timeout to fetch() the modern way: pass AbortSignal.timeout(5000) as the signal. Plus AbortController for manual cancel, combining signals with AbortSignal.any, and catching AbortError. Works in the browser and in Node.

The array methods worth reaching for in modern JavaScript: at() for negative indexing, flat(Infinity), the copying toReversed/toSorted/toSpliced/with that fix the mutation trap, and the corrected grouping API (Object.groupBy, not the Array.prototype.group that never shipped).

Read a fetch() response as it arrives instead of buffering the whole body. Async-iterate response.body, decode with TextDecoderStream, and split NDJSON line by line with a dependency-free TransformStream. The same pattern that consumes SSE and streaming LLM token responses.

Generate a UUID in JavaScript with the built-in crypto.randomUUID(), no library needed. The secure-context gotcha that returns undefined on plain http, how it works in the browser vs Node, and when you need a v7 library instead.

The modern way to register a custom Gutenberg block: a block.json metadata file, register_block_type( __DIR__ . '/build/callout' ) on the init hook, and a @wordpress/scripts build step. One source of truth for PHP and JS.

Run an array of async tasks N at a time instead of all at once. A 15-line, dependency-free promise pool (teMapLimit), the Promise.all failure mode it fixes, plus p-limit and p-map for production.

Copy and paste in JavaScript with navigator.clipboard: writeText/readText, write/read with ClipboardItem for images and HTML, the secure-context requirement, the user-gesture rule, and the clipboard-read vs clipboard-write permissions.

nvm vs fnm vs Volta, compared by speed, auto-switching, platform support, and pinning model. Which Node version manager to pick in 2026, with install commands, the .nvmrc vs package.json question, and honest caveats from running all three.

How to configure GitHub Actions setup-node properly: the minimal workflow, dependency caching, a version matrix across LTS lines, reading .nvmrc so CI matches local dev, private registry auth, and the version-drift and cache mistakes that bite in real pipelines.

Which JavaScript promise combinator to reach for: Promise.all (fail-fast), allSettled (every result, never rejects), race (first to settle), any (first fulfilled). With the gotchas that bite people.

What an async function actually is under the hood: it desugars to a generator plus a built-in runner. Plus async generators with for await...of, the AsyncFunction constructor, and why detecting async-ness is a trap.

Merge objects in JavaScript with spread or Object.assign, and learn the three gotchas that bite: spread is shallow, the last value wins, and undefined overwrites. Plus structuredClone for a real deep clone and a small recursive deep-merge helper.

The eight promise mistakes I see most in code review: unhandled rejections that now crash Node, the new Promise antipattern, missing returns in .then, forEach with an async callback, floating promises, and more. Each with a fix.

How .nvmrc node version pinning actually works, plus .node-version, package.json engines, and Volta. Which file each tool reads, how to make installs fail on a mismatch, and how to keep CI on the same version as local dev.

Read, set, append, and delete query string parameters in JavaScript with URLSearchParams: get vs getAll, the no-leading-question-mark toString gotcha, updating the URL without a reload, and the object round-trip trap.

Why a try/catch around execSync doesn't save you, why spawnSync returns instead of throws, and the one check that actually tells you a child process failed: the exit status, not whether stderr has text.

The NODE_MODULE_VERSION mismatch error means a native addon was compiled against a different Node ABI than the one now running it. Here is what the numbers mean, the one-line fix, and the Electron, Docker, and CI variants that catch people out.

Insert an item into a JavaScript array at a specific index two ways: splice(i, 0, item) mutates in place, toSpliced(i, 0, item) returns a copy (Baseline 2023, Node 20+). Plus the spread one-liner and why you should not extend Array.prototype.

The object methods I reach for daily: Object.keys/values/entries, the entries to fromEntries round-trip for transforming an object, Object.hasOwn as the modern hasOwnProperty, and why Object.freeze is only shallow.

JavaScript has no blocking sleep(). Here is the one-liner that actually works (await a Promise around setTimeout), Node's native timers/promises, a cancellable polling helper, and the setTimeout 4ms-clamp and Date.now vs performance.now gotchas.

How to uninstall Node.js cleanly on Linux, macOS, and Windows: version managers (nvm, fnm, Volta, n), the official installer, apt/dnf/brew/winget/choco, Docker, plus the leftover npm/cache/PATH files everyone forgets to delete.

Fill a JavaScript array with a constant value, build a number sequence, or do a partial fill: when to use Array(n).fill() versus Array.from({length: n}, mapper), and the reference trap that bites everyone.

How Array.reduce really works: the accumulator and current value, summing numbers, the empty-array TypeError that bites everyone, building objects and Maps, and when Object.groupBy or map/filter is the better tool.

How JavaScript promises actually work: the three states, .then/.catch/.finally, async/await as the default modern style, and the gotchas that bite (the explicit-construction antipattern and the await-in-a-loop serialization trap).

A clean modern JavaScript debounce in about ten lines: const, rest params, a leading-edge option, and a cancel method. Plus throttle, when to use which, and the React notes for 2026. No Underscore or lodash needed.

Node LTS vs Current explained: what the even/odd version lines mean, the 30-month support window, when Current is worth it, and how to pick the right line for production, libraries, and CI.

Every modern way to force a page to the top on browser refresh: window.scrollTo, the scrollRestoration API, React Router ScrollRestoration, Next.js Link scroll prop, and the events that fire on unload. Plus the hydration race that breaks naive implementations.

A practical guide to the fetch() API: the request/response model, why response.json() returns a promise, and the one surprise that bites everyone, fetch does not reject on 404 or 500. Plus headers, methods, bodies, credentials, and why fetch is now global in Node 18+.

Merge two arrays in JavaScript three ways: copy-merge with spread [...a, ...b], merge in place with a.push(...b), or copy with a.concat(b). Which to pick by mutation, memory, and the spread arg-count limit that bites on very large arrays.

Pull a random element from an array in JavaScript with one line, then shuffle the whole array correctly with Fisher-Yates. Why arr.sort(() => Math.random() - 0.5) is biased, and when to reach for crypto.getRandomValues instead of Math.random.

EADDRINUSE means another process already holds the port. Find it with lsof or netstat, kill it, or run npx kill-port. Plus the graceful in-code fix and the nodemon leftover-process trap.

Dedupe a JavaScript array. [...new Set(arr)] is the one-liner for primitives, but Set compares objects by reference, so arrays of objects need a Map keyed on a property. The gotchas with NaN and signed zero too.

Convert a NodeList, Set, arguments object, or string into a real array with Array.from or spread. When each one works, and why Array.from's map callback still earns its place over [...x].

Read and set environment variables in Node.js with process.env, and skip dotenv entirely: Node 20.6+ loads .env files natively with --env-file and process.loadEnvFile(). Plus the NODE_ENV convention, reading required vars safely, and never committing .env.

A complete, practical guide to JavaScript destructuring: pull values out of objects and arrays, skip elements, gather a rest, rename with aliases, set defaults, destructure nested shapes and function arguments, and avoid the two gotchas that bite everyone.

How to install Node.js the right way on Linux, macOS, and Windows. Covers nvm, fnm, Volta, the official nodejs.org installer, apt/dnf via NodeSource, Homebrew, winget, Chocolatey, Docker, and how to verify the install.

Run node --version and npm --version to see what you have installed. This covers every way to check Node and npm, finding which install is on PATH, reading the version inside a script, and the gotchas with version managers and multiple installs.