Overriding the base layer

Replace any component, composable or server route the base layer ships by putting a file at the same path, or import the original and wrap it through the package's deep-import map.

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.

text
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 replacePut your file at
A componentapp/components/<same/path>.vue
A composableapp/composables/<same-name>.ts
A server routeserver/api/<same/path>.<method>.ts
A layoutapp/layouts/<same-name>.vue
A blockapp/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 specifierResolves to
@revenexx/coverThe 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:

app/components/product/ProductCard.vue
<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:

app/components/product/ProductCard.vue
<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

SituationDo this
Adding to, or reordering around, an inherited componentWrap it with a deep import.
Replacing the markup entirelyShadow it.
Changing colours, radii, spacing, iconsNeither — use design tokens.
Changing what an editor can configure on a blockNeither — use block options.
Swapping where a data domain reads fromNeither — 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

Was this page helpful?