Vue Storefront is now Alokai! Learn More
File-based inheritance

File-based inheritance

File-based inheritance is a powerful feature in Alokai that enables efficient code sharing and customization across multiple stores. It allows you to maintain a consistent functionality across multiple stores while customizing specific parts of the codebase for different brands or regions.

Imagine you're managing multiple online stores:

  • A luxury fashion brand with a minimalist design
  • A sports equipment store with dynamic, high-energy UI

While each brand needs its unique look and feel, you don't want to build everything from scratch every time. You also want to share common functionality like shopping carts and checkout flows between stores, while keeping brand-specific customizations separate.

This is where Alokai's file-based inheritance comes in. It lets you:

  • Share common code between all your stores
  • Customize specific components for each brand
  • Create new stores quickly by inheriting existing functionality
  • Maintain all stores efficiently from a single codebase

For example, your luxury brand and sports store could share the same checkout flow code, while having completely different product displays and navigation.

What You'll Learn

  • Understanding file-based inheritance and its core concepts
  • Different types of stores and when to use them
  • How to create and manage stores using the CLI
  • How store hierarchy and file overrides work
  • How stores are composed and built
  • Best practices and common pitfalls to avoid

Core Concepts

What is File-Based Inheritance?

File-based inheritance allows you to reuse common code across stores while enabling the customization of specific parts.

  1. Code Reuse
    • Stores inherit all files from base applications by default
    • Common functionality is maintained in one place
    • Changes in base code automatically propagate to all stores
  2. Selective Customization
    • Stores can override specific files when needed
    • Only override what's different, inherit everything else
    • Maintain the same file path structure for overrides
  3. Hierarchical Inheritance
    • Stores can inherit from other stores
    • Multiple levels of inheritance are supported
    • More specific overrides take precedence over general ones

Project Structure

Every project starts with a single store called default.

There is nothing special within the default store, it is treated just like any other store. You can rename it to whatever you want.

Every store inherits from these base applications:

apps/storefront-unified-nextjs/  # Next.js Storefront
apps/storefront-unified-nuxt/    # Nuxt Storefront
apps/storefront-middleware/      # Middleware
apps/playwright/                 # E2E tests

These base applications contain the default implementation of all features. When you create a new store, it automatically inherits all files from these base applications. You only need to override files when you want to customize specific functionality for your store.

Store Types

Alokai supports two types of stores to accommodate different business needs:

Deployable Stores

  • Stores that are meant to be deployed and run
  • Have their own build output in .out/<store-id>
  • Can override files from ancestor stores
  • Can add store-specific files
  • Example: An actual online store for a specific brand or region

Template Stores

  • Stores that are never deployed
  • Act as shared configuration/code providers for their descendant stores
  • Used to group common overrides for a set of related stores
  • No build output in .out directory
  • Example: A regional configuration store containing shared theme and regulations for all stores in that region

Creating New Stores

To create a new store, use the store add command:

yarn store add

The CLI will guide you through configuring your store, including:

  • Setting a unique store ID
  • Choosing a parent store (or inheriting from base apps)
  • Selecting the store type (template or deployable)

For detailed information about creating and managing stores, including configuration options and best practices, see the Managing the stores guide.

Managing Store Hierarchy

Store Structure

Simple hierarchy example with deployable stores only:

apps/
β”œβ”€β”€ storefront-unified-nextjs/     # Base shared code
└── stores/
    β”œβ”€β”€ brand-a/                   # Deployable store
    β”‚   └── storefront-unified-nextjs/
    └── brand-b/                   # Deployable store
        └── storefront-unified-nextjs/

Complex hierarchy example with both deployable and template stores:

apps/
β”œβ”€β”€ storefront-unified-nextjs/          # Base shared code
└── stores/
    β”œβ”€β”€ fashion-brand/                  # Template store
    β”‚   β”œβ”€β”€ storefront-unified-nextjs/  # Shared fashion brand customizations
    β”‚   └── stores/
    β”‚       β”œβ”€β”€ us-store/              # Deployable store
    β”‚       └── eu-store/              # Deployable store
    └── sports-brand/                   # Template store
        β”œβ”€β”€ storefront-unified-nextjs/  # Shared sports brand customizations
        └── stores/
            β”œβ”€β”€ us-store/              # Deployable store
            └── eu-store/              # Deployable store

Template vs Deployable Stores

Template stores (eg. fashion-brand and sports-brand) are used to share code between their child stores, while deployable stores (eg. us-store and eu-store) are the ones that get deployed and run. Template stores can define completely different UI components and styling, customer journey flows, product presentation, and regional adaptations that will be inherited by their deployable children.

When to Use Complex Hierarchies

Complex hierarchies make sense when you have multiple brands or store families that need different:

  • Visual themes
  • Features and functionality
  • Business logic
  • Regional customizations

Composing pages

In the example above, a single page is composed of:

  • navbar component from the base app
  • body from the fashion-brand store, as it overrides the base app's body
  • footer from the us-store store, as it overrides the base app's footer and the fashion-brand store's footer

Keep it Simple

Only create inheritance levels when you need to share code between multiple stores. If you have just one brand or store family, keep everything in the base shared code (apps/storefront-unified-nextjs/).

Moving Stores

For information about moving stores between parents, see the Managing the stores guide.

File Override System

Example: Let's say you want to customize the header component for your brand-a store.

  1. Copy the original header from the base app:
# Original header
apps/storefront-unified-nextjs/components/header.tsx

# Copy it to your store's directory
apps/stores/brand-a/storefront-unified-nextjs/components/header.tsx
  1. Modify the copied file to match your brand's needs.

Now brand-a will use its own version of header.tsx, while inheriting all other files from the base application.

Always maintain the same file path structure when overriding files. For example, if you're overriding a component from apps/storefront-unified-nextjs/components/header.tsx, place it in apps/stores/brand-a/storefront-unified-nextjs/components/header.tsx in your store's directory.

File Resolution Process

When the CLI needs to compose the stores (during development or build), it follows a specific inheritance chain:

  1. First, it looks in the current store's directory
    # For example, for us-store looking for header.tsx:
    apps/stores/fashion-brand/stores/us-store/storefront-unified-nextjs/components/header.tsx
    
  2. If not found, it checks ancestor stores in reverse order (closest parent first)
    # First checks immediate parent (fashion-brand):
    apps/stores/fashion-brand/storefront-unified-nextjs/components/header.tsx
    
  3. Finally, it looks in the base applications
    # Base app is the last resort:
    apps/storefront-unified-nextjs/components/header.tsx
    

The first matching file found is used. This allows stores to:

  • Override specific files while inheriting others
  • Share common customizations through parent stores
  • Fall back to base functionality when no override exists

This resolution process applies to all file types: components, styles, configurations, etc. Understanding this helps you organize your overrides effectively.

Technical Implementation

TypeScript Configuration

The CLI automatically generates a tsconfig.json for each store. It's worth noting that the following configuration ensures the store can import files from the base storefront, ancestor stores, and the store itself:

{
  "compilerOptions": {
    "paths": {
      "@/*": ["./*", "../../../storefront-unified-nextjs/*"],
      "@sf-modules/*": ["./sf-modules/*", "../../../storefront-unified-nextjs/sf-modules/*"]
    },
    "rootDirs": ["./", "../../../storefront-unified-nextjs"]
  }
}

Do not modify tsconfig.json

The system automatically manages TypeScript configuration. Manual changes may break inheritance.

Change Detection

During the development process, the CLI:

  • Automatically detects file changes in parent stores
  • Propagates changes down the inheritance chain
  • Respects overrides in child stores

You can read more about the change detection in the Using a local environment guide.

Store Composition and Build Output

How Stores are Composed

When building stores, the system:

  1. Collects all files from the base apps (apps/storefront-unified-nextjs/, etc.)
  2. Applies overrides from parent stores in order
  3. Finally applies the store's own overrides
  4. Outputs the composed store to .out/<store-id>/

For example, with this hierarchy:

apps/
β”œβ”€β”€ storefront-unified-nextjs/     # Base shared code
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   └── ProductCard.tsx       # Original component
└── stores/
    └── fashion-brand/
        β”œβ”€β”€ storefront-unified-nextjs/
        β”‚   └── components/
        β”‚       └── ProductCard.tsx   # Override 1
        └── stores/
            └── us-store/
                └── storefront-unified-nextjs/
                    └── components/
                        └── ProductCard.tsx   # Override 2

The build process for us-store would:

  1. Start with base files from apps/storefront-unified-nextjs/
  2. Apply overrides from fashion-brand
  3. Apply overrides from us-store
  4. Output to .out/us-store/

Build Output Structure

Each deployable store is built into its own directory:

.out/
β”œβ”€β”€ fashion-brand/
β”‚   β”œβ”€β”€ storefront-unified-nextjs/   # Composed Next.js app
β”‚   β”œβ”€β”€ storefront-middleware/       # Composed middleware
β”‚   └── playwright/                 # Composed tests
└── us-store/
    β”œβ”€β”€ storefront-unified-nextjs/   # Composed Next.js app
    β”œβ”€β”€ storefront-middleware/       # Composed middleware
    └── playwright/                 # Composed tests

Template Stores

Template stores are not built into the .out directory because:

  • They are used only for sharing code with descendant stores
  • They don't have their own deployment or runtime
  • They serve as configuration templates for their descendant stores

For example, if eu-region is a template store with descendant stores fr-store and de-store, only fr-store and de-store will appear in the .out directory. Any code or configuration in eu-region will be inherited by its descendants but eu-region itself won't be built.

File Selection

The system always selects the most specific override in the inheritance chain. If a file is overridden at multiple levels, the closest override to the current store takes precedence.

Special Files

Some files receive special treatment during composition:

  • package.json: Cannot be overridden by the store. It is copied from the root apps with updated store-specific name.
  • tsconfig.json: Automatically generated for each store, based on the tsconfig.json in the root apps.

Best Practices

  1. Minimize Overrides
  • Only override files when necessary
  • Consider creating shared components in parent stores
  • Use configuration files for simple customizations
  1. Maintain Clear Structure
  • Follow consistent directory organization
  • Document store relationships
  • Keep inheritance chains shallow when possible
  1. Testing
  • Test changes across the entire inheritance chain
  • Verify overrides don't break parent functionality
  • Include tests for store-specific features

Common Pitfalls

  1. Incorrect File Placement
    • Always place files within app-specific directories
    • Maintain consistent paths across stores
  2. Breaking Changes
    • Consider the impact on child stores when modifying shared code
    • Test changes thoroughly across the inheritance chain
  3. Deep Inheritance
    • Avoid deep inheritance chains (more than 3 levels)
    • Consider flattening the structure if maintenance becomes difficult
  4. Special File Handling
    • package.json:
      • Do not create this file within store directories
      • The CLI automatically generates it in .out directory during build
      • Add all dependencies to the root app's package.json (e.g., apps/storefront-unified-nextjs/package.json)
    • tsconfig.json:
      • Do not modify this file within store directories
      • The CLI automatically generates and manages it
      • Manual changes may break the inheritance system
    • eslint.config.mjs:
      • Every app in every store has its own ESLint config file
      • By default, it uses the shared ESLint config rules present in the packages/eslint-config
      • It is recommended to use the shared ESLint config to avoid duplication and ensure consistency across the project
    • prettier.config.mjs:
      • Same as ESLint config
    • tailwind.config.ts:
      • Same as ESLint config

Next: Development - Managing the stores

Learn how to create and manage stores using the CLI.