Senior craft

Code-review checklist

The two checklists a platform team reviews against - one for modules, one for layers - and the reasoning behind each line.

Checklists are worth memorising for an interview because they compress five sections of this guide into questions you can ask about any pull request. Both lists below are ordered by how often the item actually catches something.

Module review checklist

  • Boundary: nothing in src/runtime/** imports @nuxt/kit or reads nuxt.options; imports are explicit from #imports, #app, vue, h3.
  • SSR safety: every runtime API is deterministic across server and client, browser APIs are guarded with import.meta.client, generated ids use useId() (hydration).
  • Lifetimes: no module-scope mutable state in code that runs per request; every cache has a bound, a TTL and a deliberate key; listeners, timers and observers are cleaned up (memory).
  • Options and types: typed, defaulted, validated with an actionable error; nuxt/schema augmented; nuxt typecheck passes on the playground.
  • Tests: runtime unit, fixture E2E including a hydration-warning assertion, type tests, a CI matrix including nightly.
  • Cost: setup does no network and no heavy synchronous IO; bundle delta within budget; no unnecessary global components or plugins (performance).
  • Release hygiene: compatibility declared, CHANGELOG entry, README updated, deprecation path for anything renamed.

Layer review checklist

  • Paths: every path in the layer's nuxt.config is resolved from import.meta.url; no ~ or @ aliases in layer config or layer CSS (paths).
  • Names: components, composables, app.config keys, runtimeConfig keys and server routes are all prefixed; nothing collides with a plausible app name.
  • Side effects: no plugin with a global side effect that an app cannot switch off; anything costly is opt-in through app.config or a module option.
  • Config: app.config keys typed through AppConfigInput; runtimeConfig defaults present for every key an app may override, and non-secret.
  • Server: shipped routes are namespaced, validate their input, have no debug output, and are not cached unless they are genuinely impersonal.
  • Tests: a fixture that extends the layer, an override test proving a consumer can win, nuxt typecheck, and at least one fixture that lives outside the layer's folder.
  • Docs: README documents the public surface, the three override recipes, the troubleshooting table and the versioning policy; CHANGELOG entry with a migration note if the surface changed.

How it works

Two review comments that catch the most common defects, with the diff a reviewer wants to see:

layers/base/nuxt.config.ts
-  css: ['~/assets/css/main.css'],
+  css: [join(currentDir, './app/assets/css/main.css')],

Aliases resolve against the consuming project, so this finds the app's file or nothing at all. It works in the playground because the playground lives inside the layer.

src/runtime/composables/useToolkit.ts
-const cache = new Map<string, Result>()
-
 export function useToolkit() {
+  // Per-request on the server, per-session on the client; a module-scope Map is shared
+  // by every SSR request and leaks between users.
+  const cache = useState<Record<string, Result>>('toolkit:cache', () => ({}))
   ...
 }
Gotcha· The review that passes because the playground passes

Most of the layer list exists because the playground cannot catch those defects: aliases, .env, Tailwind sources, npm packaging. A reviewer should ask "which fixture proves this?" and be satisfied only by one that lives outside the package folder.

Exercise

Exercise
  • Review nuxt-team-toolkit against the module list and fix every failure, one commit each.
  • Review your base layer against the layer list. Write down which items you could not check because no test exists, then add the smallest test that would.

Be able to say

Be able to say· What do you look for when reviewing a change to shared Nuxt tooling?

"For a module: the build-versus-runtime boundary, so nothing in runtime/ imports kit or reads options; SSR safety and determinism; no module-scope mutable state and every cache bounded; typed, validated, documented options; and tests including a fixture E2E and a version matrix. For a layer: paths resolved from import.meta.url, everything prefixed, no unswitchable global side effects, typed app.config, namespaced and validated server routes, and a fixture that lives outside the layer's folder — because the playground cannot catch the alias, .env and packaging mistakes that consumers hit first. In both cases I also ask what this change costs every consuming app in bundle size and build time."