Skip to content

Development

Start the dev servers and understand how changes flow from your editor to the browser.

Start the dev servers

bash
pnpm dev -- --store my-store.myshopify.com

This runs two servers in parallel:

ServerWhat it doesURL
shopify theme devProxies your store, serves Liquid-rendered pagesPrinted in terminal (~http://127.0.0.1:9292)
viteServes JS/CSS with hot module replacementhttp://localhost:5173

Open the Shopify CLI URL in your browser. You don't need to open the Vite URL — the theme loads assets from it automatically.

TIP

With STORE set in .env, you can run pnpm dev without the --store flag. Any flags after -- pass through to shopify theme dev.

How the dual-server setup works

The vite-tag snippet bridges Shopify and Vite. In theme/layout/theme.liquid:

liquid
{%- liquid
  render 'importmap'
  render 'vite-tag', entry: 'theme.css', preload_stylesheet: true
  render 'vite-tag', entry: 'theme.js'
-%}

In development, vite-tag points <script> and <link> tags to localhost:5173 for HMR. In production, it points to Shopify CDN URLs from the Vite build manifest.

Don't edit vite-tag.liquid or importmap.liquid — they're auto-generated by vite-plugin-shopify.

What gets hot-reloaded

File typeBehavior
CSS (theme/frontend/styles/)Instant style injection, no page reload
JS islands (theme/frontend/islands/)HMR via Vite, component re-imports without full reload
JS libraries (theme/frontend/lib/)HMR where supported, otherwise full reload
Liquid filesFull page reload after Shopify CLI syncs (~2-3 seconds)
Theme settings JSONFull page reload after Shopify CLI syncs

Editing workflow

Liquid — Edit files in theme/. Shopify CLI watches for changes, syncs them to the store, and triggers a browser reload.

CSS — The main entry point is theme/frontend/entrypoints/theme.css. Tailwind v4 is processed by the @tailwindcss/vite plugin (no tailwind.config.js). Add utility classes in Liquid templates or custom styles in theme/frontend/styles/.

Islands — Each island is a file in theme/frontend/islands/. The hydration runtime discovers custom elements in the DOM and loads matching islands automatically. Add a new file, and import.meta.glob() picks it up.

Shared JS — Utility modules live in theme/frontend/lib/. Import with the @/ alias:

js
import { debounce } from '@/lib/utils'

Tips

Port conflicts — If port 5173 is taken, Vite picks the next available port automatically. The vite-tag snippet reads the port from Vite's dev server, so this works transparently.

Multiple stores — Pass a different --store flag to work against other stores:

bash
pnpm dev -- --store staging-store.myshopify.com

Theme editor — The Shopify theme editor works alongside shopify theme dev. Edits in the editor sync back to your local files, but can overwrite local changes to JSON template/section files if you edit them simultaneously.

Lint and format — Run before committing:

bash
pnpm lint       # ESLint
pnpm format     # Prettier (Liquid, JS, CSS)

Next steps