"How would you roll out a breaking change to twenty apps?" is the most reliable senior question in a tooling interview, because the answer reveals whether you have ever had consumers. The short version: you do not ship a breaking change, you run a migration.
For a layer: renaming or removing a component, composable or util; removing or renaming an app.config key, or changing a default in a way that alters rendering; removing or renaming a runtimeConfig key; removing a page, route or layout; removing a module from the preset that apps relied on; raising the minimum Nuxt version; changing the shape of a type consumers extend.
For a module: renaming or removing an option; changing a default; removing an injected runtime API, hook or alias; raising the minimum Nuxt version; changing the emitted types in a way that fails nuxt typecheck in consuming apps.
Not breaking (but still worth a changelog line): adding an optional option, adding a component with a new prefixed name, adding a hook, widening a type.
The deprecation shim for a renamed layer component:
<script setup lang="ts">
// Deprecated in 2.4, removed in 3.0. Use <TeamButton variant="primary">.
import TeamButton from './Button.vue'
// Dev-only: never add console noise to a consumer's production build.
if (import.meta.dev) {
console.warn('[nuxt-layer-base] <TeamPrimaryButton> is deprecated and will be removed in 3.0. Use <TeamButton variant="primary">. Migration: https://git.internal/layers/base/MIGRATION.md#2-4')
}
</script>
<template>
<TeamButton variant="primary" v-bind="$attrs">
<slot />
</TeamButton>
</template>
The equivalent for a renamed module option, which must keep working and keep type-checking:
import { defineNuxtModule, useLogger } from '@nuxt/kit'
export interface ModuleOptions {
/** @deprecated since 2.4, use `analytics.enabled`. Removed in 3.0. */
trackingEnabled?: boolean
analytics: { enabled: boolean }
}
export default defineNuxtModule<ModuleOptions>({
meta: { name: 'nuxt-team-toolkit', configKey: 'toolkit' },
defaults: { analytics: { enabled: false } },
setup(options) {
const logger = useLogger('toolkit')
if (options.trackingEnabled !== undefined) {
logger.warn('`toolkit.trackingEnabled` is deprecated and will be removed in 3.0; use `toolkit.analytics.enabled`.')
// Old option still wins if the new one was not set, so nobody breaks today.
options.analytics.enabled ??= options.trackingEnabled
}
},
})
The migration note consumers actually read:
## 2.4 → 3.0
### `<TeamPrimaryButton>` removed
**Why** — one button component with a `variant` prop instead of five near-duplicates.
**Do** — replace `<TeamPrimaryButton>` with `<TeamButton variant="primary">`.
**Codemod** — `npx @team/layer-codemods primary-button .` (reviews as a normal PR).
**If you do nothing** — the component is gone in 3.0 and the build fails with "Failed to resolve component".
**Deadline** — 3.0 ships in Q1; 2.x is supported until then.
console.warn in a server-rendered component prints into the server log of a deployed app, where no developer is looking, and into production logs where it is noise. Guard with import.meta.dev, or emit the warning at build time from the module with useLogger, which lands in the terminal of the person who can act on it.
A support window is a promise, not a countdown. If the adoption report shows three apps still on the old path on removal day, removing it converts your migration into three incidents. Either extend and escalate, or do the migration in their repositories yourself.
"I do not ship it as a breaking change, I run a migration. First I check the adoption report to see who actually uses the thing, because if it is one team this is a conversation, not a process. Then a minor release where the old path still works and warns in development with a message naming the replacement, a migration note written from the consumer's side, and a codemod if the rename touches many files. I canary it in one app, announce a support window, watch adoption, and only remove it in the next major once the data says nobody is on the old path. Removing on a date instead of on evidence is how a migration becomes three incidents."
Code-review checklist
The two checklists a platform team reviews against - one for modules, one for layers - and the reasoning behind each line.
The 2026 landscape
The version snapshot as of September 2026, why an interviewer tests currency, and how to talk about what changed without reciting release notes.