A minimal, carefully structured Shopify theme with a Vite-powered asset pipeline, built for modularity, maintainability, and Shopify best practices.
-
Template-specific asset loading (no global JS bloat)
- Centralized routing in
layout/theme.liquidbased onrequest.page_type - Global bootstrap in
frontend/entrypoints/ts/theme.ts - Per-template entrypoints in
frontend/entrypoints/ts/*
- Centralized routing in
-
Vite + Shopify integration
- Fast local development with HMR
- Automatic asset manifest +
snippets/vite-tag.liquidgeneration - Remote dev support with tunnel for Shopify domain previews
-
TypeScript-ready frontend architecture
- TS entrypoints in
frontend/entrypoints/ts/ - Shared utilities in
frontend/entrypoints/ts/utils/ typecheckgate withtsc --noEmit
- TS entrypoints in
-
Core storefront interactions
- PDP variant/media/price synchronization + add-to-cart status handling
- Cart drawer with keyboard accessibility, quantity/remove async flows, and fallback to
/cart - Cart page async quantity/remove flows with empty/error states
- Collection sorting, filtering, and progressive load-more behavior
- Search predictive suggestions with graceful fallback to full search
-
Branch-linked deploy workflow
- Built assets are committed to branch for Shopify Git-connected themes
- CI validates quality gates (
typecheck,vite:build,theme-check)
-
AI-ready contributor workflow
- Generic agent guide in
AGENTS.md - Architecture + conventions in
CLAUDE.md
- Generic agent guide in
dev: Run Shopify + Vite locally (developmentenvironment)dev:remote: Run Shopify + Vite with tunnel enabledbuild: Build production assets with Vitetypecheck: Run TypeScript checks without emitting filesvite:dev: Start Vite dev server onlyvite:build: Build Vite assets onlyshopify:dev: Start Shopify theme dev server (developmentenvironment)
-
Install dependencies:
bun install
-
Create local config files:
cp .env.example .env cp example.shopify.theme.toml shopify.theme.toml
-
Set your store domain in
.env:SHOPIFY_STORE_DOMAIN=your-store.myshopify.com
-
Start local development:
bun run dev
-
If you need preview/editor on the Shopify domain:
bun run dev:remote
.
├── assets/ # Vite-built assets (hashed JS/CSS) + manifest
│ └── .vite/manifest.json
├── blocks/ # Reusable Liquid blocks
├── config/ # Theme settings
├── frontend/
│ └── entrypoints/
│ ├── css/
│ │ └── main.css # Global CSS (loaded on every page)
│ └── ts/
│ ├── theme.ts # Global bootstrap/shared setup
│ ├── index.ts # Home
│ ├── product.ts # PDP
│ ├── collection.ts # PLP
│ ├── cart.ts
│ ├── search.ts
│ ├── blog.ts
│ ├── article.ts
│ ├── page.ts
│ ├── 404.ts
│ ├── password.ts
│ ├── gift-card.ts
│ └── utils/
├── layout/
│ ├── theme.liquid # Main layout + centralized JS router
│ └── password.liquid
├── sections/ # Page sections
├── snippets/
│ └── vite-tag.liquid # Auto-generated by vite-plugin-shopify
├── templates/ # JSON templates + gift_card.liquid
├── vite.config.js
├── tsconfig.json
└── package.json- Always loaded:
css/main.cssts/theme.ts
- Template-specific:
ts/product.ts,ts/collection.ts, etc.
- Do not render
vite-tagin section files.- Asset loading is managed in layouts/templates.
-
Keep TS modules focused on behavior/state only.
- Put DOM events, async flows, and state sync in
frontend/entrypoints/ts/**.
- Put DOM events, async flows, and state sync in
-
Keep Liquid focused on markup/content structure only.
- Put editable HTML structure in
sections/**andsnippets/**.
- Put editable HTML structure in
-
Treat
data-js="..."attributes as a stable public contract between TS and Liquid.- You can restyle or rearrange markup as long as required
data-jshooks remain intact.
- You can restyle or rearrange markup as long as required
-
Search drawer safe customization points:
- Layout container and spacing in
sections/search-drawer.liquid - Result card markup inside
frontend/entrypoints/ts/search/drawer.ts(createResultItem) - Group ordering/labels inside
frontend/entrypoints/ts/search/drawer.ts(renderGroups)
- Layout container and spacing in
-
Stable
data-jscontracts by module:- Cart drawer:
cart-drawer,cart-open,cart-close,cart-items,cart-empty,cart-subtotal - Cart page:
cart-page,cart-page-items,cart-page-empty,cart-page-footer,cart-page-subtotal - Product:
product-form,option-value,thumbnail,add-to-cart,cart-status - Collection:
collection-root,collection-controls,collection-products,collection-load-more,collection-quick-buy - Search drawer:
search-drawer,search-open,search-close,search-drawer-input,search-drawer-groups
- Cart drawer:
.env(local, gitignored)SHOPIFY_STORE_DOMAIN=your-store.myshopify.com
shopify.theme.toml(local, gitignored)- Shopify CLI environment config (
development)
- Shopify CLI environment config (
example.shopify.theme.toml(committed)- Template for local environment setup
If your Shopify theme is connected directly to a Git branch, Shopify does not run Vite build for you.
Required flow:
- Run:
bun run build
- Commit generated files:
assets/*assets/.vite/manifest.jsonsnippets/vite-tag.liquid
- Push the branch
main: production-ready branch (connected to production theme)staging: QA/integration branch (connected to staging theme)feat/*: feature branches
Flow:
- Build feature on
feat/* - PR to
staging - QA on staging theme
- Merge
stagingintomain
Enforcement in CI:
- PRs to
stagingmust come fromfeat/* - PRs to
mainmust come fromstaging
git checkout staging
git pull
git checkout -b feat/<short-feature-name>Before opening a PR, run local quality gates:
bun run typecheck
bun run build
# Optional when installed locally
theme-checkIf the branch is Shopify Git-connected, commit generated build artifacts:
assets/*assets/.vite/manifest.jsonsnippets/vite-tag.liquid
Use path aliases from vite.config.js for cleaner imports:
import { addToCart } from '@ts/utils/cart';
import '@css/main.css';- TypeScript quality gate:
bun run typecheck - Liquid quality gate: Theme Check in CI (
Theme Checkjob) - Local checks:
bun run typecheckandbun run build(plus optionaltheme-check)
- CSS not loading in local preview:
- Ensure
bun run devis running.
- Ensure
- CSS not loading on
myshopify.compreview:- Use
bun run dev:remote(Chrome loopback/PNA limitation).
- Use
- Port conflicts:
lsof -nP -iTCP:5173 -sTCP:LISTENlsof -nP -iTCP:9292 -sTCP:LISTEN
This project uses the Shopify Skeleton Theme license; see LICENSE.md.