Overriding the base layer
Everything @revenexx/cover and @revenexx/cover-theme ship can be replaced from your own theme, and there are exactly two mechanisms for it. Shadowing replaces a file wholesale. Deep imports let you keep the original and wrap it.
Neither involves forking, patching, or vendoring the layer.
Shadowing: same path wins
Nuxt resolves layer files from the closest layer outwards. Put a file at the same path in your project and yours is used instead of the layer's.
node_modules/@revenexx/cover/app/components/product/ProductCard.vue ← the layer's
app/components/product/ProductCard.vue ← yours wins
That path is the whole API. There's no registration step, no config entry, and no naming convention beyond matching the path exactly. The same rule applies to composables, server routes, layouts, middleware, and public assets:
| To replace | Put your file at |
|---|---|
| A component | app/components/<same/path>.vue |
| A composable | app/composables/<same-name>.ts |
| A server route | server/api/<same/path>.<method>.ts |
| A layout | app/layouts/<same-name>.vue |
| A block | app/components/blokkli/<bundle>/index.vue |
Shadowing is total: your file is used, the layer's is not. If you only want to change part of the behaviour, wrap the original instead.
Deep imports: keep the original, wrap it
@revenexx/cover publishes an exports map so you can import its internals by path from your own code. That's what makes wrapping possible.
| Import specifier | Resolves to |
|---|---|
@revenexx/cover | The layer's nuxt.config.ts — this is what extends picks up. |
@revenexx/cover/components/* | Any component in the layer. |
@revenexx/cover/composables/* | Any composable. |
@revenexx/cover/api/* | The BFF path helpers — the /api/* route constants. |
@revenexx/cover/config/* | Shipped configuration: the palette, the theme presets, the icon map, navigation, countries. |
@revenexx/cover/interfaces/* | The TypeScript interfaces. |
@revenexx/cover/shared/* | Shared helpers. |
@revenexx/cover/validations/* | The validation rules. |
So a wrapper is a shadow file that imports the thing it replaces:
<script setup lang="ts">
// The layer's original, imported by path — this file shadows it,
// so the bare component name would resolve back to us and recurse.
import BaseProductCard from '@revenexx/cover/components/product/ProductCard.vue'
const props = defineProps<{ product: { id: string; sku: string } }>()
</script>
<template>
<div class="relative">
<BaseProductCard v-bind="props" />
<!-- the one thing this customer needs that the base card doesn't have -->
<SparePartsCompatibilityBadge :sku="props.sku" />
</div>
</template>
The important detail is the import specifier. Because your file is app/components/product/ProductCard.vue, referring to <ProductCard> by auto-imported name would resolve to your own component and recurse forever. Importing through the package path reaches the original.
A real before and after
Say the customer needs every product card to show an ERP availability lead time that the base card knows nothing about.
Before — the base layer's card renders, and there is nowhere to put the lead time. Editing node_modules is not an option, and copying the whole card into your project means owning its markup, its price widget, and its accessibility work forever.
After — one file, seven lines of real content:
<script setup lang="ts">
import BaseProductCard from '@revenexx/cover/components/product/ProductCard.vue'
const props = defineProps<{ product: { id: string; sku: string } }>()
const { data: leadTime } = await useFetch(`/api/erp/lead-time/${props.product.sku}`)
</script>
<template>
<BaseProductCard v-bind="props">
<template #footer>
<p v-if="leadTime">Available in {{ leadTime.days }} days</p>
</template>
</BaseProductCard>
</template>
You inherited the card and added one fact. When the layer improves the card — a better image loader, a fixed focus ring — you get it on the next npm update.
Note where the ERP call goes: to /api/erp/lead-time/..., a server route in your theme. Components never call an external system directly. See The BFF pattern.
Choosing between them
| Situation | Do this |
|---|---|
| Adding to, or reordering around, an inherited component | Wrap it with a deep import. |
| Replacing the markup entirely | Shadow it. |
| Changing colours, radii, spacing, icons | Neither — use design tokens. |
| Changing what an editor can configure on a block | Neither — use block options. |
| Swapping where a data domain reads from | Neither — use service modes. |
The last three rows matter more than they look. Every override is a file you now maintain against future versions of the layer; a token or an option is not. Reach for the override when the cheaper mechanisms genuinely can't express what you need.
Next steps
- The layer chain — what each layer contributes, and which to extend.
- Design tokens — the no-maintenance way to change how things look.
- Service modes — the no-maintenance way to change where data comes from.
- The BFF pattern — where your own server routes belong.