Developer Handoff · nanobag.com theme · 7 Sept 2026

SEO fixes: what to do,
where, and what not to break

Companion to the September SEO re-audit (64/100), written for the developer joining the project. Every task below names the exact file or admin screen, verified against the live site and repo on 2–7 Sept. Read the safety rules first — this repo deploys to a live store serving 14 markets, and it has bitten people.

Read this before touching anything

These are not conventions — each one exists because violating it caused a real production incident.

1
Never commit to main. Never push at all without an explicit go. Work on your own branch. main syncs to the live 14-market theme within minutes via the GitHub integration. Promotion to main is by cherry-picking a single verified commit — never by merging a branch (a wholesale merge once silently reverted live marketing copy).
2
Never run a bare shopify theme push. It full-mirrors and has wiped a colleague's draft-theme edits with no undo. Always --only <file>, and never push settings_data.json or template JSON to a theme that has editor state.
3
The GitHub sync silently skips files Shopify's parser rejects. The branch looks right while the theme serves the old file (e.g. {% # %} comments need # on every line; theme check does NOT catch it). After any promotion, verify per-FILE: shopify theme pull the file and diff it against your commit.
4
Cookiebot boot chain is an all-or-nothing set. Scripts with data-cookieconsent="ignore" (js-variables → theme.settings, jquery, vendor, theme.js, slick) must stay a complete set — a partial change reorders boot and once killed add-to-cart for every first-time EU visitor for a day (store CR −32%). Any change to script loading/order/consent attrs needs a fresh pre-consent EU pageview test.
5
shopify theme dev silently rewrites uncommitted JSON settings edits — never ship settings/template JSON that sat in the working tree while dev ran. Also: sections/collection-*-schema.liquid keep vanishing and break theme dev; restore from commit 8158327.
6
Don't touch app-owned files or the EG gift app. Free gifts are theme-native (nb_free_gift_enabled); the EG app embed is disabled but the app stays installed as rollback insurance. NanoBag/, Theme without Code updates/, backups/ are reference copies — never edit. Desktop breakpoint is 992px, not the base theme's 1024.

Admin-only tasks — zero deploy risk, do these first

No theme code involved. All four are Shopify admin changes; the theme repo is untouched.

T1Re-hide the 6 meta-catalog products from the sitemap Admin

The six *-meta-catalog products (ads-feed-only) are back in sitemap_products_1.xml — the seo.hidden metafield got wiped somewhere. They're now sitemap-listed and robots-blocked, the worst combination.

Where
Admin → each product → Metafields → seo.hidden = 1 (namespace seo, key hidden, integer)
Verify
curl -A "Mozilla/5.0" "https://nanobag.com/sitemap_products_1.xml?..." → expect 7 product URLs, zero meta-catalog
Do NOT delete, draft, or unpublish these products from their sales channels — the Meta ads feed depends on them. seo.hidden only removes them from the sitemap.

T2Hide utility collections and ghost pages Admin

In the sitemap today: /collections/eg-gift-app (internal gift plumbing), /collections/sellable-bags, /collections/nanobag-1, six placeholder style collections serving stock Shopify filler text, /pages/page (stale Winter Sale LP), and /pages/go-nanobag-eu (a 200 JS-redirect page with no <title> at all).

Where
Same seo.hidden = 1 metafield on each collection/page. Alternative for the style collections (standard/xl/sling/micro/air/pack): write 2–3 real sentences each and keep them — they're plausible landers for style queries. Ask Shubham which way before doing it.
Verify
Re-fetch sitemap_collections_1.xml / sitemap_pages_1.xml

T3Rewrite the shared product description + public product name Admin

One description (shared by 8 products including the entire Meta ads feed) still sells the discontinued Mini and Air and contradicts the on-page comparison table on all six weights. The schema also leaks the internal name "Nanobag - US" as the ProductGroup name. One rewrite propagates everywhere at once: page, JSON-LD, ads feed, MCP catalog.

Where
Admin → Products → description + title fields. Copy task — coordinate wording with Shubham.
Verify
PDP source: ProductGroup description no longer mentions Mini/Air; name is customer-facing

T4Fix the AI-agent catalog (storefront MCP) Admin Check first

POST nanobag.com/api/mcp → search_catalog("reusable shopping bag") returns 10 products: all six meta-catalog products plus market duplicates — and omits the canonical reusable-shopping-bags entirely. Any AI shopping agent asking what Nanobag sells gets the ads-only catalog. seo.hidden does not filter MCP; Online Store channel publication does.

Where
Admin → each meta-catalog product → Publishing → remove Online Store channel (keep the Facebook/Instagram channel!)
Verify
curl -X POST https://nanobag.com/api/mcp -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"search_catalog","arguments":{"query":"reusable shopping bag"}}}'
Blocked until verified: confirm the Meta feed does not require Online Store publication for these products. If it does, this task is off the table — say so and move on.

Theme code tasks — deploy risk, follow the rules above

T5Emit aggregateRating — the cheapest rich-result win Theme

Judge.me server-renders data-average-rating='4.84' / 708 reviews (note: single quotes — double-quote greps miss it), but no schema block on the site emits aggregateRating. Third audit in a row. This unlocks star ratings in product SERPs.

Option A
Judge.me admin → Settings → check for a "Rich Snippets" / structured-data toggle. Zero code. Try this first.
Option B
Small JSON-LD snippet in layout/theme.liquid (existing schema blocks live around lines 328–486 on the Shubham branch; re-check against main) reading Judge.me's synced metafields (product.metafields.reviews.rating.value / reviews.rating_count.value — verify they're populated before relying on them). Emit a Product node with aggregateRating using the same @id the native ProductGroup uses so Google merges them.
Verify
Google Rich Results Test on /products/reusable-shopping-bags — must show Product with rating, no errors. Curl the raw HTML too: grep -c aggregateRating → ≥1.

T6Ship the jQuery/slick deferral Theme Highest risk

Commit e12ee58 ("defer jquery+slick, drop Font Awesome, async slick.css, cap video poller" — touches layout/theme.liquid, sections/new-video-slideshow.liquid, snippets/modal-video.liquid) sits on the Shubham branch, never promoted. Live head still loads jquery.min.js and slick.js parser-blocking. Lighthouse mobile today: PDP 30/100, LCP 13.8s, TBT 10s.

How
Cherry-pick e12ee58 onto a fresh branch off current origin/main — do not port files wholesale; the commit is 14 months of drift old, resolve conflicts against main's current file versions line by line.
Verify
Push to a test theme (--only the 3 files), then: (a) fresh pre-consent EU pageview — variant picker, add-to-cart, and gallery must work before accepting cookies; (b) new-video-slideshow sections still initialize (that section historically needed parser-blocking jQuery — this is exactly the conflict to test); (c) after promotion, CLI-pull each of the 3 files and diff vs the commit (rule 3).
This touches the exact scripts behind the Aug-19 EU incident (rule 4). Do not ship without the pre-consent EU test. The repo's qa-bot has a consent-guard check (QA_CONSENTCHECK=1) that watches this invariant — run it.

T7Kill the {!= form_name !} H1 leak Theme

A literal Zipify template string ships as a real second <h1> on the PDP, homepage, and pages: <h1 class="zpa-crm-popup-title">{!= form_name !}</h1>. Source found: snippets/page-footer.zipifypages.liquid.

How
The popup markup is a client-side template being rendered as live DOM. Wrap it in <template> tags, or if the Zipify CRM popup is confirmed dead, remove the render. Check first whether any live page actually uses the popup.
Verify
curl -s https://nanobag.com/ | grep -c 'form_name' → 0, and the PDP drops to one <h1>
Caution: *.zipifypages.liquid snippets are written by the Zipify Pages app — it may regenerate the file on its next publish. Note the change somewhere durable, and if Zipify is still actively used, prefer the <template> wrap (survives regeneration semantics better than deletion) and re-check after any Zipify publish.

T8PDP H1 renders the variant, not the product Theme

Google sees <h1>Standard Black</h1> on the money page. Root cause is deliberate code in sections/new-main-product.liquid:283: unless product.has_only_default_variant → h1_title = selected_or_first_available_variant.options | join ' '.

How
H1 gets product.title; render the variant options in a non-heading element below it. But: the PDP variant shim rewrites this title client-side on variant change (the "XL-Duck" title behavior) and the <split-words> animation wrapper has its own NBSP-eating gotcha (shielded in commit 8663c98). Read the shim before changing the markup — the shim must keep targeting whatever element now carries the variant text.
Verify
Variant switching still updates the visible title instantly (no 2–4s lag regression), and curl shows the product name in the H1

Script task

T9Translate product SEO fields for 8 non-English locales Script

/de-de/products/reusable-shopping-bags serves the byte-identical English title tag and meta description — product-level SEO fields were never translated (bodies and homepages are). Every non-English market competes in Google with an English snippet.

How
Use the existing translationsRegister pattern — working examples in scripts/register-homepage-translations.mjs and scripts/register-pl-website.mjs (GraphQL Admin API, one-off Node scripts, npm only). Target the product resource's meta_title / meta_description translatable keys per locale.
Verify
curl -A "Mozilla/5.0" https://nanobag.com/de-de/products/reusable-shopping-bags | grep '<title>' → German
Translations are registered per-resource and can be wiped by editor re-saves for section translations — product-field translations are safer, but keep the script idempotent so it can re-run.

T10Alt text on product gallery images Admin

40 of 138 <img> on the PDP lack alt text. Part of the remainder is payment/review-widget SVGs — ignore those. The repo has a ready-made worklist: nanobag-alt-text-audit.html in the theme repo root covers the product gallery set. Alt lives on the images in Shopify admin, not in theme code.

Working agreements

How changes actually reach production here.

→
Flow: edit on your branch → push to a test theme with --only → Shubham reviews and QAs → single verified commit is cherry-picked to main → per-file CLI-pull verification. You never publish.
→
When something ships: NANOBAG-DOCS.md (repo root) is the status board + shiplog — dated entry in the same session it goes live.
→
Per-market overrides: templates/*.context.<market>.json files are deliberate — never collapse or "clean up". Market context files override parent template content.
→
Live site fetching: nanobag.com 503s plain curl — use a browser UA with retries. Storefront fetches go through a CDN and can lie about what just deployed; the CLI file-pull is the source of truth.