Skip to content
On this page

Troubleshooting ​

Common failure modes when adding a section, in order of frequency.

"My section doesn't show up in the library" ​

Cause 1 — the registry didn't pick it up.

Restart npm run dev. Vite resolves import.meta.glob at boot, so new folders aren't seen until restart. Yes, HMR is great for editing, but folder creation needs a full restart.

Cause 2 — manifest has no type field.

Open the dev console. You should see:

[sectionRegistry] manifest at .../MySection/manifest.json has no .type, skipped

Add "type": "MySection" to the JSON.

Cause 3 — folder name doesn't match manifest.type.

Folder must equal type exactly. Banner/manifest.json with "type": "banner" (lowercase) won't be wired up. Watch for typos.

Cause 4 — JSON is invalid.

Trailing commas, missing quotes, unescaped characters. The dev server log shows the parse error. Run the file through any JSON linter to double-check.

Cause 5 — manifest index isn't refreshed.

The builder reads public/sections/manifest-index.json, not the individual manifests. npm run dev regenerates the index on boot. If you edit a manifest while dev is running, hard-refresh the builder (Ctrl+Shift+R) — it'll re-fetch the index.

"My section renders blank" ​

Cause 1 — your component reads props.config.title directly.

It's possible the merchant hasn't typed anything yet. Read defensively:

ts
const title = computed(() => t(props.config?.title) || 'Default title');

Cause 2 — translatable field read without useT().

The manifest has "translatable": true, so the value is { en: "...", fr: "...", ar: "..." }. Reading props.config.title directly gives you the object, which Vue renders as [object Object] (or blank). Always:

ts
const t = useT();
const title = computed(() => t(props.config?.title));

Cause 3 — missing image fallback.

If the merchant hasn't uploaded an image yet, props.config.image?.src is undefined. Render a placeholder or hide the <img>:

vue
<img v-if="config.image?.src" :src="config.image.src" :alt="config.image.alt || ''" />

"Editing a field doesn't update the preview" ​

Cause 1 — non-reactive read.

You stored the value at mount:

ts
// ❌ Wrong — captures value once
const title = ref(props.config.title);

Use a computed:

ts
// ✅ Right — re-reads on every change
const title = computed(() => props.config?.title);

Cause 2 — manifest path doesn't match component's read.

Manifest field declares "path": "primaryCta.text", but the component reads props.config.cta.text. Builder writes to config.primaryCta.text, your component reads from config.cta.text — different keys. Fix one to match the other.

Cause 3 — builder isn't connected.

Top-left of the builder shows a status pill. If it says "Connecting...", the postMessage bridge never completed and patches aren't being sent. Hard-refresh the builder tab. If still stuck, ask Abdel — bridge issues are deploy / origin issues, not section-code issues.

"My styleKey doesn't work" ​

Cause 1 — styleKeys in manifest doesn't match useStyles in component.

Manifest:

json
"styleKeys": [{ "key": "headline", "label": "Headline" }]

Component must call:

ts
const headlineStyles = useStyles('headline', () => props.config?.styles);

Same string, both places.

Cause 2 — you forgot to bind attrs.

useStyles returns { attrs }. The styles only apply when you bind them:

vue
<h2
  :class="headlineStyles.attrs.value.class"
  :style="headlineStyles.attrs.value.style"
  :data-element-key="headlineStyles.attrs.value['data-element-key']"
>

The data-element-key attribute is what the builder uses to highlight the right element. Without it, hover-in-Style-panel does nothing.

Cause 3 — wrong scope for repeating elements.

Card title in a grid — every card has its own <h3>. If you use ['self'], only the first one gets styled. Use a descendant selector instead:

ts
const cardTitle = useStyles('cardTitle', () => props.config?.styles, [], '.card-title');

Apply the class to every card title:

vue
<h3 class="card-title">{{ title }}</h3>

"My data-driven section shows 'Loading…' forever or empty results" ​

Cause 1 — no auth token in dev.

Your section calls $storeino.products.search(...) (or .collections, .pages, etc.), but local souk-theme has no JWT to authenticate the call with. The Network tab shows 401 Unauthorized from api-stores.storeino.world.

Fix: set NUXT_PUBLIC_AUTH_TOKEN in souk-theme/.env. See Getting started → Connecting to a store, Mode 2. After editing .env, restart npm run dev — env vars are read once at boot.

Cause 2 — token expired.

Tokens copied from browser cookies typically last an hour. If yesterday's setup worked but today's doesn't, your token aged out. Ask Abdel for a long-lived test token, or re-copy from a fresh browser session.

Cause 3 — token belongs to a store without products.

The token authenticates as a specific store. If that store has no products, products.search returns { results: [] } — which renders as your empty state, not an error. Sanity-check by hitting https://api-stores.storeino.world/api/products/search?limit=1 directly in a browser with your token in the headers (use a REST client like Insomnia / Postman).

"Server log says theme load failed" ​

This is not a section bug — it's an infrastructure issue. The storefront's SSR couldn't fetch the theme document from api-stores. Causes:

  • theme-manager proxy down on the dev server
  • api-stores down
  • The store doesn't have an active theme

Section work waits until the storefront loads at all. Ask Abdel; they'll know which service to restart.

"Save button doesn't seem to do anything" ​

Check the Network tab for a POST /api/themes/update request. If it returns 200, the save succeeded. If you don't see the request, the builder's bridge isn't ready (see "builder isn't connected" above).

After saving, the storefront iframe may show cached content for up to 120 seconds because of theme-manager's cache. To see the change on a shopper-style fresh visit, clear the cache:

bash
curl -X POST http://theme-manager-host:7070/api/cache/clear \
  -H 'x-auth-token: <cacheSecret>' \
  -H 'Content-Type: application/json' \
  -d '{"subdomain":"<merchant-subdomain>"}'

Ask Abdel for the cache secret. Or just wait 2 minutes.

"Hydration mismatch" warning in console ​

Your component renders different HTML on the server vs the client. Usual culprits:

  • Reading window or document at top level of <script setup>.
  • Using Date.now(), Math.random(), or any time-zone-dependent value during render.
  • Conditionally rendering based on import.meta.env.DEV only on the client.

Wrap browser-only code in onMounted() or <ClientOnly>. Or compute the value identically on both sides.

When all else fails ​

Look at how a similar existing section handles the case. The 50+ sections in components/sections/ cover most patterns — find one that's close to what you're building and read it end-to-end. Copy the patterns, don't reinvent.

If you're still stuck, ping Abdel with:

  • What you're trying to do
  • What you tried
  • The exact console log + Network tab response
  • A link to your section's index.vue and manifest.json

A reproducible scenario gets a fast answer.

Released under the MIT License.