frontends icon indicating copy to clipboard operation
frontends copied to clipboard

Nuxt Layers & Theming for Shopware Composable Frontends

Open mdanilowicz opened this issue 9 months ago • 3 comments

TL;DR: Deliver a first‑class theming and scaffolding system for Shopware Composable Frontends, based on Nuxt Layers, ensuring developers can spin up a performant, SEO‑ready, and extensible storefront with minimal setup and maximum consistency.



🎯 Why This Matters


Currently:


  • Developers must scaffold many core UI pages (checkout, PDP, PLP, account) manually (#1784).

  • Core UI components (filters, sliders, product cards) are missing or incomplete (#1765).

  • No standardized theme layer exists — every project reinvents design tokens, structure, and theming approach.

The result:


  • Slower time‑to‑market (~6–8 weeks average for a serious storefront integration).
  • 
Inconsistent implementations across agencies and merchants.

  • High ramp‑up cost for new developers unfamiliar with Shopware’s composables.

Solving this unblocks adoption of Shopware Frontends by:


  • Reducing initial setup costs for agencies.

  • Creating a consistent DX pattern.

  • Improving perceived maturity of the Composable Frontends offering.

👤 User Personas


  1. Frontend Developers @ Agencies: Need templates & components that just work so they can focus on customization instead of rewriting boilerplate.
 Pain today: struggle with missing docs, broken SSR, and manual scaffolding.

  2. Merchant Tech Leads: Want confidence that a Shopware project can launch in weeks, not months. Pain today: poor predictability in cost/timeline because every storefront starts from scratch.

  3. Shopware Core Developers / Ecosystem Maintainers: Need consistency across community contributions.
 Pain today: smaller contributors reinvent different component APIs, leading to fragmentation.

🚀 Deliverables


  • A Nuxt Layers Theme package published to npm.

  • A Composable Frontends starter storefront using that theme.

  • Full set of core pages + components shipped in layers (extensible, documented).

  • DX docs + sandbox examples (Stackblitz templates); these shorten ramp-up and ensure “time-to-first-storefront” is minimal.
    • #528
    • #1291
    • #1181

A closer look

Theme Layer (this epic): 
* A base Nuxt Layer Theme exists with design tokens (colors, typography, spacing).
 
* Developers can extend/override the theme via configuration (not copy-paste).
 
* Theme switching and multi‑theme support documented with examples.
 
* TCO lens/keeping theme recent, getting fixes and updates from Shopware: "either 1) you extend the template, so you create own versions only of the components you want; 2) you copy the template and own whole thing; need to add SW changes on your own to avoid breaking your site. Additionally for things like API client, composables, helpers we have npm packages so this is only a matter of updating their versions and adjusting to eventual changes."— @patzick 
* Measure: A new developer can start a themed storefront in <1 hour (baseline today: ~1–2 days).

Pages (#1784): 
* Core customer‑facing pages exist out-of-the-box (Checkout, PDP, PLP, Landing, My Account, Registration).
 
* Each page uses fully wired composables with extensibility slots.
 
* Measure: Number of “must‑implement” pages reduced to zero; project setup time drops by ~20%.

Components (#1765): 
* All shared components (buttons, dropdowns, filters, product card, cart item, gallery, slider, etc.) are implemented as Nuxt Layer components.
 
* Fix known SSR and multilingual blockers to drive consistency across locales and SSR: #1965, #1795, #1761, #184. 
* The storefront theme must "just work" with SEO (OG tags, alternate links) and caching defaults: #1631, #312, #1739, #618. 
* Payment Flow Docs + Integration → #1484, #1478, #1847, #538. Essential for Checkout experience in Nuxt Layers pages. 
* 
Composable extensibility to ensure agencies can adapt the theme cleanly without forking code: #1493, #1450, #536, #1270.
 
* Measure: New intakes/developers use >80% of provided components without modification in first 3 sample storefronts tested.

Documentation & DX: 
* Guides exist for applying, customizing, and extending layers.
 
* Demo repository provided (Stackblitz‑ready, no blockers).
 
* Onboarding dev survey shows reduction in “time to first hello world storefront” to [X time-TBD]. 
* Checkout pages ship payment integrations documented with async flows.


⏳ Cost of Delay


  • Agency inefficiency: Every project loses dev hrs and money on boilerplate theming and pages.

  • Adoption cliff: Agencies trial Shopware but churn because initial experience feels incomplete.
  • 
Competitive risk: Other headless solutions (e.g. Vue Storefront, Next.js Commerce) have more polished “starter kits”.

✅ Acceptance Criteria


  • Time-to-first storefront: new dev gets a themed shop running in <1 hour.

  • Dev feedback survey: >=80% agree “Composable Frontends are production-ready” in agency partner survey.

Earlier notes / more context

Assumptions

Structure

We propose constructing a complex structure with two levels of layers. The domain layers provide foundational components for each domain, such as checkout, product, account, and so on. The template layer, on the other hand, leverages these domain layers to form the overall template structure.

Image

Domain layers can work without template layer, but template layer cannot without domain layers.

Types

Each layer should depend on @shopware/api-client to utilise the types generated from OpenAPI.

<script setup lang="ts">
import type { Schemas } from "@shopware/api-client/store-api-types";

type Product = Schemas["Product"];

const props = defineProps<{
  product: Product;
}>();
</script>

<template>
  <div>{{ product }}</div>
</template>

Business logic / component communication

(?) We agree to prepare to create 2 POCs

  • [ ] Reuse composables + add new UI composables
  • [ ] Use events/hooks for the communication (no composable dependencies)

Translations

Each layer should depend on the vue-i18n module. Translations in .json files in the module

Context data

Context data refers to global information that should be accessible throughout the application. For example, it includes data such as currency, date format, and other session-specific details that need to be fetched only once

The decision on how it will be handled will be made after the Business Logic / Component Communication POC

Solution to the problem

  • [x] Use Storybook as a wrapper for the new layers/components
  • [x] Develop some essential components
  • [x] Use basic components in 2-3 examples to prove that this concept is working
  • [ ] Develop all components from the Figma project
  • [ ] Create a template from the Figma project

mdanilowicz avatar Mar 20 '25 18:03 mdanilowicz

@mdanilowicz @patzick @BrocksiNet this is a rough summary of our PoC and the recent assumptions.

Nuxt Template Layer — Base Architecture & Extensions

This repository provides a reusable Nuxt layer (template) that acts as the foundation for building Nuxt 3 storefronts and CMS-driven apps. It is designed to be extended, themed, and customized across brands and deployment types.


Architecture Overview

@shopware/composables/layer     ← business logic (cart, product, account, etc.)
@shopware/cms-base/layer        ← CMS rendering (slots, CMS routes, blocks)
             ▲
             │
         template              ← pages, layouts, UI components, theme, plugins

What the template Layer Includes (nuxt 3 structure yet)

Directory Purpose
pages/ App routes like homepage, checkout, login, product, etc.
layouts/ Global and route-specific layout shells
components/ UI components (CMS-aware, fully themed)
assets/ CSS tokens, design system, fonts
composables/ Project-specific logic (optional)
plugins/ Project-specific plugins (e.g. analytics, injections)
app.vue Root shell
nuxt.config.ts Extends external logic layers and defines app settings
uno.config.ts Definitions for UnoCSS - tailwind presets by default

Extending the Template

You can create many types of projects by extending the template layer. Below are real-world scenarios:

1. Single Page Application (SPA)

// projects/spa/nuxt.config.ts
export default defineNuxtConfig({
  extends: ['@your-org/nuxt-template-layer'],
  pages: false,
  css: ['~/assets/css/spa.css'],
});

With a custom app.vue:

<template>
  <LandingPage />
  <ProductShowcase />
  <ContactForm />
</template>

2. Dark Mode Variant using UnoCSS

Enable UnoCSS in the base template:

// template/nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@unocss/nuxt'],
  unocss: {
    dark: 'class',
  },
});

Extend with a dark-mode layer:

// projects/dark-mode-a/nuxt.config.ts
export default defineNuxtConfig({
  extends: ['@your-org/nuxt-template-layer'],
  appConfig: {
    theme: 'dark',
  },
});

Use dark class in layout:

<template>
  <div :class="{ dark: $config.public.theme === 'dark' }">
    <NuxtPage />
  </div>
</template>

Style using UnoCSS utilities:

<div class="bg-white text-black dark:bg-neutral-900 dark:text-white p-4">
  This box changes in dark mode.
</div>

3. Brand-Specific Storefront

// projects/brand-x/nuxt.config.ts
export default defineNuxtConfig({
  extends: ['@your-org/nuxt-template-layer'],
  css: ['~/assets/css/brand-x.css'],
  runtimeConfig: {
    public: {
      shopName: 'BrandX',
      currency: 'EUR',
    }
  }
});

Add overrides:

/projects/brand-x/components/Header.vue
/projects/brand-x/assets/css/brand-x.css

4. Internationalized Version

// projects/template-intl/nuxt.config.ts
export default defineNuxtConfig({
  extends: ['@your-org/nuxt-template-layer'],
  modules: ['@nuxtjs/i18n'],
  i18n: {
    locales: ['en', 'de', 'fr'],
    defaultLocale: 'en',
    vueI18n: './i18n.config.ts',
  },
});

Add locale files in /locales.


5. Developer Sandbox

// projects/dev-sandbox/nuxt.config.ts
export default defineNuxtConfig({
  extends: ['@your-org/nuxt-template-layer'],
  runtimeConfig: {
    public: {
      useMockData: true,
    }
  }
});

In a composable:

const config = useRuntimeConfig();
if (config.public.useMockData) {
  return fetchMockProducts();
}

External Dependencies

This layer depends on:


Best Practices

  • Use the template layer for all app-level logic, layouts, and overrides
  • Extend it for custom branding, internationalization, or simplified UIs
  • Keep logic in @shopware/composables/layer, and use slots/components for display

mkucmus avatar Apr 15 '25 11:04 mkucmus

Abandoned Architecture Approaches with Nuxt Layers

During the planning and implementation of our layered Nuxt architecture, we explored several patterns and structures. Below is a list of the strategies we considered but ultimately did not adopt, along with reasons for abandoning them.


1. Feature-Specific Logic Layers

Proposed Structure

  • cart-logic/
  • checkout-logic/
  • product-logic/

Each logic layer would provide composables and utilities for its domain, consumed by separate UI layers.

Why We Abandoned It

  • Introduced too many layers and cross-dependencies
  • Harder to track where a composable originated
  • Increased boilerplate and coordination between logic and UI
  • Required extra configuration and plugin resolution across layers

2. UI Per Feature Domain (e.g., ui-cart, ui-checkout)

Proposed Structure

  • ui-cart/
  • ui-checkout/
  • ui-product/

Each feature would have its own UI layer, depending on its logic layer.

Why We Abandoned It

  • Too fragmented: lots of small packages to maintain
  • Increased difficulty when components needed to be shared across features
  • Styling, theming, and global config became harder to manage
  • Composing multiple UI layers into one project added unnecessary complexity

3. Event Bus with mitt for Communication Between Layers

Proposed Idea

Use a global event bus to loosely couple components and logic across layers.

eventBus.emit('cart:add', { item });

Why We Abandoned It

  • Lacked type safety and visibility
  • Harder to debug and trace app behavior
  • Risk of memory leaks without careful lifecycle management
  • Not well suited for Nuxt's SSR and plugin lifecycle

4. Provide/Inject Pattern for Core Logic Access

Proposed Idea

Manually provide logic via plugins and inject it in components.

provide('cart', useCart());

Why We Abandoned It

  • More verbose than using auto-imported composables
  • Complicates overrides and dynamic extension across layers
  • Adds friction for developers unfamiliar with Vue’s provide/inject API

5. Layer Per Domain (e.g., Cart, Checkout) with Shared Core

Proposed Stack

core/
  └─ useCart()
cart-logic/
  └─ extends core
cart/
  └─ extends cart-logic

Why We Abandoned It

  • Too deeply nested for our needs
  • Hard to manage overrides (which layer should replace useCart?)
  • Difficult to onboard new contributors
  • Risked introducing circular dependencies if not carefully structured

Conclusion

While Nuxt layers are powerful, they can also lead to over-architected solutions that are harder to maintain. We chose to simplify by:

  • Providing one self-contained template layer
  • Extending only external logic layers (@shopware/composables/layer, @shopware/cms-base/layer)
  • Consolidating all UI, layout, and routing into the template itself

This approach allows for scalability, customization, and maintainability without unnecessary complexity.

mkucmus avatar Apr 15 '25 11:04 mkucmus

Work plan from late Aug 2025

This aims to drive small increments of value for faster feedback loops.

Increment 1: Lay down the base Nuxt Layer, unblock onboarding, fix reliability blockers, and ship developer docs scaffolding.

1️⃣ Nuxt Layer Base & Theme Infrastructure

  • Implement the core Nuxt Layer (colors, typography, spacing, theme config) (this epic)
    • Provide default theme with override/extension mechanism.




2️⃣ Essential Reliability Fixes (Blockers)


  • Fix SSR language/currency inconsistencies #1795.

  • Patch mixed language after login/logout #1965.

  • Fix language switch on subpages #1761.

  • Ensure category route generation is correct #1842.



3️⃣ Onboarding / DX


  • Draft step-by-step deployment/setup doc #1291
  • resolve dead internal links #1181

4️⃣ Ship First Core Components

  • Implement buttons, input fields, basic layout containers
  • Document composable override patterns #1450, #536

Increment 2: Deliver feature-complete customer-facing pages using the layer, provide all critical UI building blocks, and ensure performance/SEO are first-class.

1️⃣ Pages (Plug & Play)—implement and document:


  • Product Listing Page (PLP) #1786
  • Product Detail Page (PDP) #1787
  • Checkout Page #1788 
* My Account, Registration, Addresses, Cart, Order History #1921, #1919, #1926, #1925, #1924, #1923, #1922


2️⃣ Themed Components Library—Deliver: 


  • product card [#1782], cart item #1783, filter dropdown #1776], sidebar filter #1775, slider #1777, dropdown navigation #1781, checkbox #1773, switch #1774, etc. 
* Ensure accessibility, SSR/SEO-compliance.

  • Ensure “kitchen sink” page is available in demo.


3️⃣ Payment & Async Flows—integrate and document async payment handling (PayPal Express, Braintree, etc.) #1484, #1478, #538, #1847.


4️⃣ Performance & SEO Defaults


  • ship caching strategies, edge caching docs #1631, #312.

  • Implement: alternate language links #1739, OpenGraph tags.
  • 
Lighthouse/SEO optimization as acceptance gating.


5️⃣ Feedback Loops


  • Run agency/user intake: time to first styled storefront, pain points, gather survey metrics.

lasomethingsomething avatar Aug 21 '25 13:08 lasomethingsomething