All notes

Never throw at module load in Next.js

BuddingPlanted Jun 26, 2026Last tended Jul 19, 2026nextjspatternstil

Never throw at module load in Next.js

My first next build on a fresh app failed with a surprise: the database client threw DATABASE_URL is not set — during the build, on a machine that will never talk to the database.

The mechanism: next build imports your modules during page-data collection with NODE_ENV=production but without your runtime environment. Any module that throws at the top level — the classic "fail fast" env guard — aborts the entire build:

// ✗ kills next build on any machine without the env var
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) throw new Error("DATABASE_URL is not set");

The fix is to degrade instead of dying — expose readiness as a value:

const databaseUrl = process.env.DATABASE_URL;

export const db = databaseUrl
  ? drizzle(neon(databaseUrl), { schema })
  : (null as unknown as DrizzleDb);

/** True when a real DB connection is configured. */
export const dbReady = Boolean(databaseUrl);

The query layer checks dbReady and returns empty results; the UI renders an honest "database not connected yet" setup state. The build passes on any machine, the dev server boots before Neon exists, and the page tells you exactly what's missing.

"Fail fast" is still right — but at request time, in the query layer, where a missing env var is actually an error. Import time is the wrong court for that trial.

This lesson came from the same app as Two apps, one Neon database.

Linked from

  • Two apps, one Neon database

    Sharing one Postgres between two apps works fine — if you prefix your tables and treat drizzle db:push as a loaded gun. Versioned migrations only.

Related notes

  • Next.js 16: middleware.ts is now proxy.ts

    Next 16 renamed the middleware file convention to proxy.ts. Same API, better name — and a reminder that it should stay a cheap presence check, never your only auth gate.

  • TIL: Postgres websearch_to_tsquery

    Postgres ships a full-text query parser that understands Google-like syntax out of the box — quoted phrases, OR, and -exclusions — no hand-rolled parser required: