title | layout |
---|---|
File-based inheritance |
default |
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
::list{type="success"}
- 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 ::
File-based inheritance allows you to reuse common code across stores while enabling the customization of specific parts.
-
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
-
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
-
Hierarchical Inheritance
- Stores can inherit from other stores
- Multiple levels of inheritance are supported
- More specific overrides take precedence over general ones
Every project starts with a single store called default
.
:::tip
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.
Alokai supports two types of stores to accommodate different business needs:
- 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
- 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
To create a new store, use the store add
command:
yarn alokai store add
:::tip
run yarn alokai list
to see all availble commands
:::
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)
:::tip For detailed information about creating and managing stores, including configuration options and best practices, see the Managing the stores guide. :::
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
:::tip 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.
:::
Complex hierarchies make sense when you have multiple brands or store families that need different:
- Visual themes
- Features and functionality
- Business logic
- Regional customizations
In the example above, a single page is composed of:
navbar
component from the base appbody
from thefashion-brand
store, as it overrides the base app'sbody
footer
from theus-store
store, as it overrides the base app'sfooter
and thefashion-brand
store'sfooter
:::warning 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/
).
:::
:::tip Moving Stores For information about moving stores between parents, see the Managing the stores guide. :::
Example: Let's say you want to customize the header component for your brand-a
store.
- 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
- 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.
:::warning
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.
:::
When the CLI needs to compose the stores (during development or build), it follows a specific inheritance chain:
-
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
-
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
-
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
:::tip This resolution process applies to all file types: components, styles, configurations, etc. Understanding this helps you organize your overrides effectively. :::
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"]
}
}
:::warning Do not modify tsconfig.json The system automatically manages TypeScript configuration. Manual changes may break inheritance. :::
During the development process, the CLI:
- Automatically detects file changes in parent stores
- Propagates changes down the inheritance chain
- Respects overrides in child stores
:::tip You can read more about the change detection in the Using a local environment guide. :::
When building stores, the system:
- Collects all files from the base apps (
apps/storefront-unified-nextjs/
, etc.) - Applies overrides from parent stores in order
- Finally applies the store's own overrides
- 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:
- Start with base files from
apps/storefront-unified-nextjs/
- Apply overrides from
fashion-brand
- Apply overrides from
us-store
- Output to
.out/us-store/
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 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.
:::info 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. :::
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 thetsconfig.json
in the root apps.
- Minimize Overrides
- Only override files when necessary
- Consider creating shared components in parent stores
- Use configuration files for simple customizations
- Maintain Clear Structure
- Follow consistent directory organization
- Document store relationships
- Keep inheritance chains shallow when possible
- Testing
- Test changes across the entire inheritance chain
- Verify overrides don't break parent functionality
- Include tests for store-specific features
-
Incorrect File Placement
- Always place files within app-specific directories
- Maintain consistent paths across stores
-
Breaking Changes
- Consider the impact on child stores when modifying shared code
- Test changes thoroughly across the inheritance chain
-
Deep Inheritance
- Avoid deep inheritance chains (more than 3 levels)
- Consider flattening the structure if maintenance becomes difficult
-
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
::card{title="Next: Development - Managing the stores" icon="tabler:number-2-small" }
#description Learn how to create and manage stores using the CLI.
#cta :::docs-arrow-link{to="/guides/multistore/tooling-and-concepts/managing-the-stores"} Next ::: ::