Skip to content

Implement layered architecture for easy upstream updates #7

@dlhck

Description

@dlhck

Layered Architecture Implementation Plan

Overview

Separate "Vendure core" code from "merchant customizable" code to enable:

  • Easy upstream updates via git merge
  • Clear boundaries between editable and protected code
  • Theme and component customization without touching core files

Approach: Incremental migration with core/ and merchant/ naming. Slot system deferred.


Target Directory Structure

/src/
├── core/                          # VENDURE-MAINTAINED (do not modify)
│   ├── components/
│   │   ├── commerce/              # ProductCard, ProductGrid, etc.
│   │   ├── layout/                # Navbar, Footer, HeroSection
│   │   └── shared/                # Pagination, skeletons
│   ├── lib/
│   │   └── vendure/               # GraphQL API, queries, mutations
│   ├── hooks/
│   ├── contexts/
│   └── theme/
│       └── base.css               # Core design tokens
│
├── merchant/                      # MERCHANT-CUSTOMIZABLE
│   ├── components/
│   │   ├── overrides/             # Replace core components by name
│   │   └── custom/                # New merchant-specific components
│   ├── theme/
│   │   ├── tokens.css             # Design token overrides
│   │   └── components.css         # Component style overrides
│   └── lib/
│       └── graphql/               # Custom queries/fragments
│
├── config/                        # CONFIGURATION
│   ├── storefront.config.ts       # Store settings, features
│   ├── theme.config.ts            # Logo, fonts, layout
│   └── components.config.ts       # Component overrides registry
│
├── components/ui/                 # shadcn/ui (unchanged, shared)
├── app/                           # Next.js routes (merchant-owned)
└── graphql.ts                     # gql.tada (unchanged)

Implementation Phases

Phase 1: Infrastructure Setup

Goal: Create directory structure and update configuration without breaking existing code.

  1. Create directories:

    src/core/
    src/core/components/commerce/
    src/core/components/layout/
    src/core/components/shared/
    src/core/lib/vendure/
    src/core/hooks/
    src/core/contexts/
    src/core/theme/
    src/merchant/
    src/merchant/components/overrides/
    src/merchant/components/custom/
    src/merchant/theme/
    src/merchant/lib/graphql/
    src/config/
    
  2. Update tsconfig.json - Add path aliases:

    "paths": {
      "@/*": ["./src/*"],
      "@core/*": ["./src/core/*"],
      "@merchant/*": ["./src/merchant/*"],
      "@config/*": ["./src/config/*"]
    }
  3. Create empty placeholder files:

    • src/core/theme/base.css (empty)
    • src/merchant/theme/tokens.css (empty)
    • src/merchant/theme/components.css (empty)
  4. Verify build: npm run build


Phase 2: Extract Theme Tokens

Goal: Move CSS variables to layered theme files.

  1. Create src/core/theme/base.css:

    • Move :root and .dark CSS variable definitions from globals.css
    • Keep @layer base styles
  2. Update src/app/globals.css:

    @import "tailwindcss";
    @import "tw-animate-css";
    @import "@core/theme/base.css";
    @import "@merchant/theme/tokens.css";
    @import "@merchant/theme/components.css";
    
    @custom-variant dark (&:is(.dark *));
    
    @theme inline {
      /* ... existing mappings ... */
    }
  3. Verify: Dark/light theme switching still works.


Phase 3: Extract Commerce Components

Goal: Move commerce components to core, update imports.

  1. Move files:

    • src/components/commerce/*src/core/components/commerce/
  2. Update imports in app routes:

    • Find all files importing from @/components/commerce/
    • Update to @core/components/commerce/
  3. Files affected:

    • src/app/page.tsx (featured-products)
    • src/app/product/[slug]/page.tsx
    • src/app/collection/[slug]/page.tsx
    • src/app/search/page.tsx
    • src/app/account/orders/page.tsx
  4. Verify: npm run build


Phase 4: Extract Layout Components

Goal: Move layout components to core.

  1. Move files:

    • src/components/layout/*src/core/components/layout/
  2. Update imports:

    • src/app/layout.tsx (Navbar, Footer)
    • src/app/page.tsx (HeroSection)
  3. Verify: npm run build


Phase 5: Extract Shared Components

Goal: Move shared utilities and skeletons.

  1. Move files:

    • src/components/shared/*src/core/components/shared/
  2. Update imports in collection and search pages.

  3. Verify: npm run build


Phase 6: Extract Library Code

Goal: Move Vendure API, contexts, hooks to core.

  1. Move files:

    • src/lib/vendure/*src/core/lib/vendure/
    • src/lib/utils.tssrc/core/lib/utils.ts
    • src/lib/format.tssrc/core/lib/format.ts
    • src/contexts/*src/core/contexts/
    • src/hooks/*src/core/hooks/
  2. Keep in original location (merchant-owned):

    • src/lib/metadata.ts (merchant customizes site name)
    • src/lib/auth.ts (may need merchant customization)
  3. Update all imports across the codebase.

  4. Verify: npm run build


Phase 7: Component Registry

Goal: Create registry pattern for component overrides.

  1. Create src/config/components.config.ts:

    // Core component imports
    import { ProductCard } from '@core/components/commerce/product-card';
    import { ProductGrid } from '@core/components/commerce/product-grid';
    import { Navbar } from '@core/components/layout/navbar';
    import { Footer } from '@core/components/layout/footer';
    import { HeroSection } from '@core/components/layout/hero-section';
    
    // Merchant overrides (uncomment to enable)
    // import { ProductCard } from '@merchant/components/overrides/ProductCard';
    
    export const components = {
      ProductCard,
      ProductGrid,
      Navbar,
      Footer,
      HeroSection,
    } as const;
    
    export type Components = typeof components;
  2. Update src/app/layout.tsx:

    import { components } from '@config/components.config';
    const { Navbar, Footer } = components;
  3. Update other pages to use registry pattern.


Phase 8: Configuration Files

Goal: Create storefront and theme configuration.

  1. Create src/config/storefront.config.ts:

    export const storefrontConfig = {
      store: {
        name: process.env.NEXT_PUBLIC_SITE_NAME || 'Vendure Store',
        supportEmail: 'support@example.com',
      },
      features: {
        wishlist: false,
        productReviews: false,
        guestCheckout: true,
      },
      search: {
        productsPerPage: 12,
        defaultSort: 'newest' as const,
      },
    };
  2. Create src/config/theme.config.ts:

    export const themeConfig = {
      brand: {
        logo: '/logo.svg',
        logoAlt: 'Store Logo',
      },
      layout: {
        maxWidth: '1280px',
        navbarPosition: 'sticky' as const,
      },
    };

Phase 9: Merchant Override Example

Goal: Create example override to demonstrate the pattern.

  1. Create src/merchant/components/overrides/ProductCard.tsx:

    • Copy from core, add a small customization (e.g., badge)
    • Document as example
  2. Enable in components.config.ts (commented out by default).


Phase 10: Git Configuration & Documentation

Goal: Set up merge strategies and documentation.

  1. Create CUSTOMIZATION.md:

    • Theme customization guide
    • Component override guide
    • GraphQL extension guide
  2. Create UPGRADING.md:

    • Upstream sync workflow
    • Conflict resolution guide

Critical Files Modified

File Changes
tsconfig.json Add @core/*, @merchant/*, @config/* path aliases
src/app/globals.css Restructure to import layered CSS files
src/app/layout.tsx Use component registry for Navbar/Footer
src/app/page.tsx Use component registry, update imports
All route files Update imports from @/components/* to @core/components/*

Key Mechanisms

Component Override Pattern

// In components.config.ts, swap the import:
import { ProductCard } from '@merchant/components/overrides/ProductCard';

Theme Override Pattern

/* In merchant/theme/tokens.css */
:root {
  --primary: oklch(0.65 0.2 145); /* Override to green */
}

CSS Component Override Pattern

/* In merchant/theme/components.css */
[data-component="product-card"] {
  border-radius: 0;
}

Verification

After each phase:

  1. npm run build - Ensure no TypeScript/build errors
  2. npm run dev - Verify pages render correctly
  3. Test dark/light theme toggle

Final verification:

  1. Create a sample merchant override
  2. Enable it in components.config.ts
  3. Verify the override renders instead of core
  4. Test git merge simulation with a test branch

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions