SEO Audit & Implementation Guide

knicpouches.com

Shopify DTC — KNIC nicotine pouches (Dawn 15.2.0 theme). Eight parallel specialist analyses plus a second implementation-grade pass: paste-ready Liquid, schema, and compliance copy with exact file locations.

Prepared July 17, 2026 · Field data via Google CrUX & PageSpeed Insights · Regulatory text verified against eCFR 21 CFR Part 1143

53/ 100

Clean foundation, capped ceiling

The technical build is solid — SSR, correct canonicals, passing Core Web Vitals in the field. What holds the score down: thin content, a 3-product architecture with no room to rank, and compliance gaps that are a legal problem before they're an SEO one.

Category breakdown

Content Qualityweight 23%
38
Technical SEOweight 22%
68
On-Page SEOweight 20%
48
Schemaweight 10%
50
Performanceweight 10%
78
AI Searchweight 10%
49
Imagesweight 5%
45

Read this first

Compliance — beyond SEO

This is a regulatory problem before it's a ranking problem

Confirmed absent from the served HTML across every page (grepped, product and static pages):

  • No FDA nicotine warning anywhere. 21 CFR 1143.3 requires the exact text "WARNING: This product contains nicotine. Nicotine is an addictive chemical." on labeling and advertising — and the rule explicitly names "Internet Web pages" as covered advertising. Zyn, On!, and Rogue show it on every product page.
  • Wrong regulatory framework. The footer carries a DSHEA dietary-supplement disclaimer (21 CFR 101.93) on every page. Nicotine pouches are FDA Center for Tobacco Products / PMTA products — wrong bucket, no PMTA mention, loose "all-natural" claims.
  • Zero NAP. The contact page is a bare form — no company name, address, phone, or email. Customers can't verify the seller or report an adverse event.

✓ Age verification is in place — a client-side gate via the Good-Apps app (which is why a raw HTML fetch didn't see it). It just needs a dedupe (it loads twice) and a 21+/cookie-remember config check. Everything above still stands. Not legal advice — get counsel on the warning and PMTA status before driving more traffic in.

The strategic reality

Organic + owned channels are the only ones open

Nicotine is a restricted or prohibited category on Google Shopping & Merchant Center, Meta/Instagram ads, and Amazon. Paid acquisition is structurally closed — don't spend time trying to get Merchant Center approved. Growth has to come from organic search, email/SMS, affiliate (UpPromote is already installed), and direct/brand.

Against that, the architecture is the core problem: 3 products, one collection that isn't linked anywhere, and 84 variants collapsed onto a single URL. No page can rank for "wintergreen nicotine pouches" or "8mg nicotine pouches." Every ranking competitor runs per-flavor and per-strength indexable URLs. The comparison-shopper and subscription-buyer personas scored 23/100 — not because the pages are weak, but because the pages don't exist. Good news found in the deep dive: subscribe-and-save already exists (Casa Subscriptions, 10% monthly) — it just isn't surfaced.

Implementation guide — by workstream

Tap a workstream to expand. Each fix lists the exact Shopify/Dawn location, paste-ready code where relevant, and expected impact. All Liquid verified against the live Shopify/dawn repo.

1

Compliance & trust

FDA warning · disclaimer swap · NAP · age-gate dedupe

CriticalAdd the FDA nicotine warning, site-wide

Exact required text, verbatim (cannot be paraphrased): "WARNING: This product contains nicotine. Nicotine is an addictive chemical." On advertisements — which §1143.3(b) explicitly extends to "Internet Web pages" — it must sit in the upper portion, occupy ≥20% of the ad area, be ≥12pt Arial/Helvetica bold, high-contrast, and be enclosed in a rectangular border.

Where: new snippet snippets/nicotine-warning.liquid, rendered in layout/theme.liquid right after <body> so it's sitewide and can't be removed by section edits; plus a larger instance in sections/main-product.liquid under the buy-box.

<!-- snippets/nicotine-warning.liquid -->
<div class="nicotine-warning" role="note" aria-label="Nicotine warning">
  <p>WARNING: THIS PRODUCT CONTAINS NICOTINE. NICOTINE IS AN ADDICTIVE CHEMICAL.</p>
</div>

<style>
.nicotine-warning{background:#000;color:#fff;border:4px solid #fff;
  outline:3px solid #000;text-align:center;padding:.75rem 1rem;
  font-family:Arial,Helvetica,sans-serif;font-weight:700;
  font-size:max(12pt,1rem);letter-spacing:.02em}
</style>

Also flag to whoever runs paid social / label QA: the same warning must be burned into ad creative and appear on both principal display panels of packaging (≥30% of each panel) — neither is a theme fix.

CriticalReplace the wrong disclaimer

The footer currently carries, on every page: "These statements and products…have not been evaluated by the Food and Drug Administration…not intended to diagnose, prevent, treat or cure…" — that's the dietary-supplement structure/function disclaimer (21 CFR 101.93). It's the wrong framework for a tobacco-product and signals regulatory illiteracy. Remove it from product/ingredient/GMP pages.

Where: sections/footer.liquid (the rich-text/liquid block rendering that text).

Replace with the mandatory §1143.3 warning line, plus recommended risk language (model on Zyn/On!/Rogue — counsel to sign off on final wording):

This product is intended for adult nicotine users 21 years of age or older. Not for sale to minors. Keep out of reach of children and pets. Not for use by women who are pregnant or breastfeeding, or by individuals with heart disease, high blood pressure, diabetes, or who are sensitive to nicotine. If accidentally ingested, contact Poison Control immediately at 1-800-222-1222.

Open item for legal: the site discloses nothing about PMTA status. Post-2022, FDA CTP authority extends to tobacco-free/synthetic nicotine; marketing without a marketing order is a live enforcement risk industry-wide. Needs a direct answer from regulatory/legal — not verifiable from the public site.

CriticalAdd real NAP + Organization schema

Put a real legal entity name, mailing address, support email and phone on /pages/contact (above the existing form) and a one-line NAP in the footer. Then wire the same values into Organization JSON-LD (see Workstream 3) so AI systems and Google can resolve "who makes this." Requires KNIC to supply real, verifiable values — don't ship placeholders.

HighDedupe + verify the age gate

The Good-Apps age gate loads twice: a legacy manual <script> in theme.liquid (fires on window.load) and the modern App Embed block. Delete the legacy snippet.

Where: Admin → Edit code → layout/theme.liquid → search good_apps_timer / goodAvAppsData / asyncLoad → delete that block. Keep the App Embed (Customize → App embeds). Verify one request in DevTools → Network.

Config check: confirm it's a hard gate (blocks content, not a dismissible overlay), set to 21+ (federal Tobacco 21 minimum — not 18+), with real DOB entry and a cookie so returning visitors aren't re-gated every page.

Removes ~85KB duplicate JS transfer + parse
2

Site architecture

Flavor/strength collections · nav · subscription landing

CriticalBuild ~18 flavor/strength collections — not 60 thin products

Splitting 84 variants into dozens of product pages would fragment the 29 pooled Judge.me reviews and read as doorway pages. Instead build tag-driven automated collections (zero new products): 2 strength, 10–14 flavor, 1 bundle, plus the existing /collections/all (currently orphaned — link it).

  • Strength: /collections/4mg-nicotine-pouches, /collections/8mg-nicotine-pouches
  • Flavor: /collections/spearmint-nicotine-pouches, …/wintergreen-…, …/grape-…, etc.
  • Bundles: /collections/nicotine-pouch-bundles
How: tag the product (flavor_spearmint, strength_4mg…); build each as an Automated collection with condition "Product tag equals …". Each collection canonicals to itself (unique curated copy). Product page canonical must stay the bare URL with no query string — Dawn already does this; don't let a dev make it per-variant.

HighRebuild nav + add breadcrumbs + footer shop links

Replace the flat 5-item menu with a Shop mega-menu (By Strength / By Flavor / Bundles / Subscribe & Save). Add a breadcrumb trail Home > Shop > [Collection] > [Product] (schema in Workstream 3). Footer is currently 100% legal links — add a Shop column so discovery equity flows sitewide.

HighSurface the existing subscription

Subscribe-and-save already runs via Casa Subscriptions (10% monthly). Don't migrate apps. Build /pages/subscribe-and-save targeting "nicotine pouch subscription," and add the recurring price as a second Offer in the product JSON-LD so the discounted price is crawlable.

MediumIndex-bloat guardrails + GTIN/MPN

  • Keep the existing robots.txt filter-trap disallows (*sort_by*, *+*, *filter*&*filter*) — they auto-cover new collections. Cap Search & Discovery filters at 2 dimensions (Flavor, Strength); keep pack-size on the product page only.
  • Existing SKUs are 12-digit UPC-A → usable as gtin12. Backfill the Barcode field on all 84 variants in Admin, then emit "gtin12": "{{ variant.barcode }}" in the product JSON-LD (stock Dawn doesn't map barcode→gtin). Multipacks need their own GS1 GTINs.
3

Structured data

Organization · Product + AggregateRating · Breadcrumb · ItemList

HighFix broken Organization sameAs

Live value is ["#","#","",…] — 9 placeholders matching Dawn's 9 social settings, hand-looped with bad fallbacks. Replace with a loop that only emits filled-in social settings, and fix @context to https.

Where: Admin → Edit code → search theme for sameAs (likely layout/theme.liquid or a custom snippet). Merchant must also fill the 9 social fields in Theme settings, or leave the array legitimately empty.
{%- assign keys = 'social_facebook_link,social_instagram_link,social_youtube_link,social_tiktok_link,social_twitter_link,social_snapchat_link,social_pinterest_link,social_tumblr_link,social_vimeo_link' | split: ',' -%}
<script type="application/ld+json">
{
  "@context":"https://schema.org","@type":"Organization",
  "name":{{ shop.name | json }},"url":"{{ request.origin }}",
  {%- if settings.logo != blank %}"logo":"https:{{ settings.logo | image_url: width: 500 }}",{% endif -%}
  "sameAs":[
  {%- assign n = 0 -%}
  {%- for k in keys -%}{%- assign link = settings[k] -%}
    {%- if link != blank -%}{%- if n > 0 %},{% endif %}{{ link | json }}{%- assign n = n | plus: 1 -%}{%- endif -%}
  {%- endfor -%}]
}
</script>

HighProduct schema with AggregateRating (4.93★ / 29)

Current product schema is Shopify's native {{ product | structured_data }} filter — closed, not extensible, so it can't carry ratings. Replace it. First check whether Judge.me's own schema toggle is on (it's currently emitting zero) — if you hand-roll and Judge.me turns on, you get duplicate AggregateRating (a validation error). Pick one source.

Where: sections/main-product.liquid ~lines 749–751. Verify Judge.me's metafield namespace in Admin → Custom data → Products before relying on the metafield read.
<script type="application/ld+json">
{
  "@context":"https://schema.org",
  "@id":"{{ request.origin }}{{ product.url }}#product",
  "@type":"ProductGroup",
  "name":{{ product.title | json }},
  "url":"{{ request.origin }}{{ product.url }}",
  "description":{{ product.description | strip_html | json }},
  "productGroupID":"{{ product.id }}",
  "brand":{"@type":"Brand","name":{{ product.vendor | json }}},
  "aggregateRating":{
    "@type":"AggregateRating",
    "ratingValue":"{{ product.metafields.reviews.rating.value.rating | default: 4.93 }}",
    "reviewCount":"{{ product.metafields.reviews.rating_count.value | default: 29 }}"
  },
  "hasVariant":[
  {%- for v in product.variants -%}
    {"@type":"Product",
     "@id":"{{ request.origin }}{{ product.url }}?variant={{ v.id }}#variant",
     "name":{{ v.title | prepend: product.title | prepend: ' – ' | json }},
     {%- if v.sku != blank %}"sku":{{ v.sku | json }},{% endif -%}
     "image":"https:{{ v.featured_image | default: product.featured_image | image_url: width: 1920 }}",
     "offers":{"@type":"Offer",
       "url":"{{ request.origin }}{{ product.url }}?variant={{ v.id }}",
       "priceCurrency":"{{ cart.currency.iso_code }}",
       "price":"{{ v.price | money_without_currency | strip_html }}",
       "availability":"https://schema.org/{% if v.available %}InStock{% else %}OutOfStock{% endif %}"}
    }{% unless forloop.last %},{% endunless %}
  {%- endfor -%}]
}
</script>

Data-only fixes (Admin, not code): backfill SKU on the 44 "5 Pack" variants; fix variant image/name mismatches (a "Spearmint / 4mg / 5 Pack" variant points at the Grape photo — do a full 84-variant pass, this was a 3-variant spot check).

MediumBreadcrumbList + ItemList/CollectionPage

Dawn ships neither. Add a schema-only snippets/breadcrumbs.liquid (rendered at top of main-product.liquid and main-collection-product-grid.liquid) and an ItemList/CollectionPage block before the collection loop (~line 160). Keep ItemList minimal (ListItem + url) — each product's own schema lives on its page. FAQPage skipped — Google restricted it to gov/health in Aug 2023; no rich-result benefit for a commercial nicotine site.

4

Performance

Bootstrap removal · images · LCP · script deferral

Field CWV already pass at p75 (LCP 1.59s, INP 91ms, CLS 0), but the page is 6.2MB and fragile on slow devices. These protect the margin and fix a real product-page LCP delay.

HighRemove Bootstrap (CSS + JS)

bootstrap.min.css loads render-blocking on every page and is 97% unused (26,004 of 26,863 bytes). bootstrap.bundle.min.js loads unconditionally with zero data-bs-* usage anywhere — it does nothing. Grid classes appear only in 4 homepage Custom Liquid blocks; replace with ~20–30 lines of scoped CSS grid/flex, then delete both tags from theme.liquid.

~810ms render-blocking (rolled-up estimate)

HighFix the image pipeline

The worst images are raw <img src> tags pasted into Custom Liquid with no width param, so the CDN serves full-res PNGs (flavors-bg.png 747KB, product cans 318–393KB) into ~400px boxes. Shopify's CDN already auto-serves AVIF/WebP via content-negotiation — you just need a width so it resizes. Run the images through image_url with srcset.

<img src="{{ 'flavors.png' | file_url | image_url: width: 750 }}"
  srcset="{{ 'flavors.png' | file_url | image_url: width: 375 }} 375w,
          {{ 'flavors.png' | file_url | image_url: width: 750 }} 750w,
          {{ 'flavors.png' | file_url | image_url: width: 1100 }} 1100w"
  sizes="(min-width:750px) 50vw, 100vw"
  width="750" height="..." alt="KNIC pouch flavors" loading="lazy">
1,424 KB — biggest single byte-weight win

MediumPrioritize the product-page hero

Homepage hero is already correct (fetchpriority="high" on slide 1). The product page active-slide <img> has no fetchpriority at all — the cause of the 511ms LCP "resource load delay" CrUX reports. Gate it on forloop.index == 1 in snippets/product-media.liquid, or minimally add fetchpriority="high" to the .slider__slide.is-active image.

Targets 511ms product-page LCP delay

MediumDedupe Good-Apps + defer GTM

The age-gate dedupe (Workstream 1) is also the single cleanest perf win (~85KB). Beyond that: FB Pixel / Klaviyo / Judge.me run through Shopify's Web Pixels Manager (already deferred ~10s — not yours to touch). GTM via the Stape proxy (bhs.knicpouches.com) is yours — wrap it in requestIdleCallback. Before touching anything Meta-related, confirm with the ad-account owner whether the separate meta-capi-param-builder is an intentional CAPI setup or an orphaned duplicate.

5

Technical

503 playbook · duplicate title · cart · headers

CriticalThe intermittent 503 playbook

Bursty, not constant (couldn't reproduce on a later 100-request pass). The error body is Shopify's own error-pages template, not a Cloudflare-branded edge error — so it's the Shopify storefront-renderer origin erroring, not the proxy. This is Shopify-managed infra (x-dc: gcp-us-central1); there is almost certainly no separate Cloudflare dashboard to log into.

  • Set up 1-min external monitoring (UptimeRobot/Pingdom) on /, /robots.txt, /sitemap.xml to catch it in the act with a real failure rate.
  • On a live 503, capture x-request-id + the server-timing requestID, then open a Shopify Support ticket with timestamps — only they have origin logs.
  • Rule apps in/out by disabling one at a time: Good-Apps timer → Judge.me app embed → note hCaptcha is Shopify-native (cluster on form pages = escalate, can't uninstall).

CriticalFix the homepage title + H1 + meta description

Root cause found: Dawn's dedup guard uses case-sensitive contains, and homepage title "Knic Pouches" ≠ store name "KNIC Pouches", so the suffix double-appends → "Knic Pouches – KNIC Pouches".

  • Zero-code fix: Admin → Online Store → Preferences → set a keyword-rich Homepage title (e.g. "KNIC Pouches — Tobacco-Free Nicotine Pouches | 10 Flavors, 4mg & 8mg") and fill the empty Homepage meta description.
  • H1: Customize → Home → top banner section → set the Heading to include "Tobacco-Free Nicotine Pouches" (homepage currently has no H1).
  • Permanent: make the guard case-insensitive: {%- unless page_title | downcase contains shop.name | downcase %} in snippets/meta-tags.liquid.
  • Re-enter the truncated meta descriptions on /about and /ingredients (Admin → Pages → SEO listing).

LowCart indexability + headers

Bare /cart escapes the Disallow: /cart/ rule (prefix mismatch). In templates/robots.txt.liquid change to Disallow: /cart (drops the slash → covers both), and add a conditional <meta name="robots" content="noindex,follow"> when template contains 'cart'. HSTS/CSP hardening is Shopify-platform controlled — document as a known ceiling, not an action item. www→non-www 301 confirmed clean (8/8).

6

Content & E-E-A-T

Blog plan · About rewrite · trust signals

HighTurn on the blog — 10 posts mapped to intent

Dead since Sept 2024, zero posts. One of the few organic levers open to this category. Structure every post answer-first: a 40–60 word direct answer in paragraph one, 4–6 question-H2s, an FAQ block, inline citations to FDA/peer-reviewed sources.

  • Are Nicotine Pouches Safer Than Vaping or Cigarettes?
  • 4mg vs 8mg Nicotine Pouches: How to Choose Your Strength
  • Is Synthetic Nicotine Regulated by the FDA? (turns the PMTA gap into a trust asset)
  • How to Quit Vaping With Nicotine Pouches (matches the homepage testimonial)
  • KNIC Flavor Guide (internal-linking hub to all flavor collections)
  • + tobacco-free manufacturing, duration/absorption, pregnancy/heart-condition safety, GMP explained, safe storage/disposal

HighRewrite the About page for E-E-A-T

Currently 90 words of templated copy, zero named people, zero location. Add named founder(s) with bios/photos, manufacturing location, a real founding narrative, concrete facility/cert specifics (the GMP page's "100+ years combined experience" is unattributed), and cross-links to Ingredients/GMP/Contact. This is the one place more words alone won't help — it needs real, verifiable people and facts.

MediumFix content-accuracy bugs

Copy says "10 flavors" but 14 are listed; homepage flavor card misspells "Cinammon" and reuses one beach description for both cinnamon and raspberry-lemon; ingredients table has typos ("Distilld," "Propylparabe," "Bicardonate"). Small, but they undercut expertise on an ingestible product.

7

GEO / AI search

AI-crawler robots.txt · llms.txt · authority · passage rewrites

Genuinely ahead of the curve on agentic commerce (verified /agents.md, /.well-known/ucp, live UCP/MCP endpoint with wired payment handlers — set up to be bought from by ChatGPT/Shop agents, not just cited). The ceiling is authority: an independent search found zero third-party mentions of "KNIC Pouches."

MediumDifferentiated AI-crawler robots.txt

The store already has a custom templates/robots.txt.liquid. Add explicit groups: allow GPTBot, OAI-SearchBot, ClaudeBot, PerplexityBot, Google-Extended (visibility); block CCBot, anthropic-ai, cohere-ai (training-only). Note GPTBot is dual-purpose — allowing citation also allows training scrape.

User-agent: GPTBot
Allow: /
User-agent: OAI-SearchBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: PerplexityBot
Allow: /
User-agent: Google-Extended
Allow: /

User-agent: CCBot
Disallow: /
User-agent: anthropic-ai
Disallow: /

MediumShip a real llms.txt

Current /llms.txt just mirrors agents.md. Replace with a spec-shaped index (H1 + summary blockquote + grouped links to products, ingredients, GMP, UCP endpoints). It's served by a Cloudflare Worker route, not Shopify — swap that route's body. Leave the (empty) blog out until posts exist. Note: sitemap_agentic_discovery.xml is Shopify-generated and can't be hand-edited — reference llms.txt from agents.md and robots.txt instead.

MediumPassage rewrites for AI extraction

Front-load direct facts. Example for the ingredients page:

What's in a KNIC Nicotine Pouch?
KNIC Nicotine Pouches contain five ingredients: tobacco-free nicotine (salt-based, from pure leaf) in 4mg or 8mg strength, a plant-based stabilizer (under 2%), natural flavor with stevia (under 2%), a sodium bicarbonate pH balancer (under 1%), and a microcrystalline filler, sealed in an all-natural cotton pouch.56 words · question-H2 + self-contained answer = the shape AI Overviews lift verbatim

HighBuild an off-site footprint

Standard playbook mostly doesn't apply (Reddit/YouTube/Meta ban nicotine ads). Open lane = organic disclosed content: (1) Trustpilot + on-site reviews first — closes the schema gap too; (2) seed sample packs to independent pouch reviewers on r/ZYN, r/nicotine, YouTube unboxers (not as the brand account); (3) owned YouTube education explainers (informational, not ad-format). Deprioritize Wikipedia until real press exists.

Sequencing

Week 1 — legal + no-code: counsel review of warning/PMTA; add the FDA warning snippet; swap the disclaimer; add NAP; dedupe the age gate; fix homepage title/H1/meta (all Admin-editable).

Weeks 2–3 — theme PR: one Dawn PR bundling Bootstrap removal + image pipeline + product-hero fetchpriority + the corrected schema (Organization, Product+AggregateRating, Breadcrumb, ItemList). Backfill SKUs/variant images in Admin.

Ongoing — organic engine: build the flavor/strength collections + subscription landing page + nav; ship the blog cadence; start the off-site review/mention build. This is the only channel open, so it's where the real growth is.

Regulatory notes are informational, not legal advice — confirm warning placement, disclaimer wording, and PMTA status with qualified counsel before publishing. All Liquid should be tested on a duplicate/unpublished theme before going live.