When a project runs B2C and B2B (or US vs. CA, or dealer vs. consumer) as separate sales channels, http://127.0.0.1:8000 alone is not enough. You need the browser to send a Host header that matches the sales channel’s domain mapping—otherwise you are always testing the default channel and shipping bugs reproduce only in staging.
This note is the recipe we used so QA and engineers could switch channels without editing .env every hour.
1. Why hosts file + real DNS-shaped names
Shopware resolves the active sales channel using request URL (and related config). Using:
b2c.testb2b.test
…pointed to 127.0.0.1 gives you distinct Host headers while staying offline.
mkcert / TLS (optional but realistic)
Some payment or cookie flows behave differently on HTTPS. mkcert local certs for *.test remove “works on HTTP only” surprises.
2. Admin configuration checklist
-
Sales channels → domain
Add each local host to the correct channel. Watch for http vs https—must match how you browse. -
Language + currency per channel
Wrong defaults cause “tax looks fine on B2C but B2B is EUR” confusion that is not a tax bug. -
Snippet / theme assignment
If B2B uses a different theme or snippet set, verifytheme:dump/ storefront config points at the right compile target. -
Trusted proxies
Behind Vite dev proxy or Caddy, Symfony must trust forwarded headers or redirects loop.
3. Storefront + Vite
Problem: HMR websocket defaults to one host; second host breaks hot reload.
Mitigations we used
- Drive
APP_URL/ storefrontproxyconfig from one primary dev URL for asset compilation. - For secondary hosts, accept full page reload during dev or run a second
npm run devwith explicit--host/ port mapping (document the trade-off for the team).
Cookies
- Do not set cookie domain to
.testunless you intentionally share sessions betweenb2c.testandb2b.test. - Isolated carts prevented “I added SKU on B2C and it appeared in B2B cart” false positives.
4. Debugging: prove the right channel
Add a temporary Twig snippet or toolbar entry (only dev) printing:
context.salesChannel.idcontext.salesChannel.name- Active language id
Compare across hosts on the same product URL pattern.
CLI parity
bin/console sales-channel:list
Cross-check UUIDs against what the storefront shows—admin typos here burn hours.
5. API and headless
If Store API clients pass sw-access-key only, they may bypass host-based resolution—ensure integration tests still set sales channel header (sw-sales-channel-id) where the API requires it.
6. Docker alternative (same ideas)
If you use docker-compose instead of bare PHP:
- Map multiple
extra_hostsor Traefik routes (b2c.localhost,b2b.localhost). - Mount
var/per container user sotheme:compiledoes not permission-fight.
Resume framing
“Set up multi-host local Shopware with correct sales-channel resolution, Vite-aware dev limits, and documented cookie/session boundaries—reduced channel-specific bugs caught late in QA.”