Skip to content

SEO & Metadata

Status: Audit v0.1 (2026-05-09).

1. Rendering model

Property Value Source
Rendering SPAssr: false nuxt.config.js:2
SSG Available via yarn generate (writes dist/) package.json:11
Hybrid SSR Not used ssr: false precludes

The app is a single-page application. Search engine bots that don't execute JavaScript (some legacy bots, low-priority Bingbot crawl modes) see only the empty shell. Modern Googlebot does execute JS, so the impact is mostly on:

  • Non-Google search engines
  • Social-media link unfurlers (Twitter card, LinkedIn preview, Slack, Discord) — these typically do not execute JS
  • Email clients with link previews

Finding [WC-SEO-1] (Severity: Medium for marketing pages; Low for the authenticated app): Marketing/landing pages and public surfaces (privacy policy, terms, plan info, special offers) all render as a JS-required SPA. Social link previews will be empty.

The authenticated app surfaces (/<accid>/contentplanner, etc.) are not crawlable by design (auth-gated), so SEO doesn't matter there. The concern is the public surface.

2. Page metadata

Every Nuxt 2 page can declare a head block:

export default {
  head() {
    return {
      title: 'Page Title',
      meta: [{ hid: 'description', name: 'description', content: '...' }]
    }
  }
}

nuxt.config.js:6-22 declares the global head:

head: {
  title: '',
  titleTemplate: titleChunk => titleChunk ? `${titleChunk} | Someli` : 'Someli',
  meta: [
    { charset: 'utf-8' },
    { name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0' },
    { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
  ]
}

process.env.npm_package_description = 'someli social media library' (per package.json:4).

Findings:

  • No Open Graph (OG) tags at the global level. No og:title, og:description, og:image, og:type, og:url.
  • No Twitter Card tags (twitter:card, twitter:image, etc.).
  • No canonical URL at global or per-page level.
  • maximum-scale=1, user-scalable=0 in the viewport — prevents zoom, an accessibility issue. Removing maximum-scale and user-scalable allows assistive zoom.

Finding [WC-SEO-2] (Severity: Medium): No OG / Twitter Card tags. Sharing any public URL will produce a blank preview. Phase 0 — add baseline OG tags at the global level (logo, brand description) plus per-page overrides for marketing pages.

Finding [WC-SEO-3] (Severity: Medium — overlaps with accessibility.md): The viewport maximum-scale=1, user-scalable=0 violates WCAG 2.1 1.4.4 (Resize text) for users who need zoom. Remove these directives.

3. Per-page head declarations

grep -rEn "head\(\)" pages 2>/dev/null \| wc -l[VERIFY-SEO-1] count pages with custom head declarations.

Pages are grouped via data/sitemap.js for the dashboard "node-active" navigation. There's a middleware/head.js that reads from Sitemap and dispatches updateHead({title, description}) to the store — but head.js is not wired (see routing-and-state.md). So per-page titles via this mechanism are not actually setting page titles.

store/index.js:11-12 defines pageTitle and pageDescription in root state, with mutations setPageTitle and setPageDescription. These are bound somewhere (likely via the head() function in a layout), but the source of mutations (updateHead action via head.js) is unwired.

Finding [WC-SEO-4] (Severity: Medium): The per-page title/description mechanism is partially built but not connected. Wiring head.js middleware (or moving its head-related logic into another middleware) would activate per-page titles. Phase 0 effort.

4. Structured data (JSON-LD)

grep -rEn 'application/ld\+json\|@type"' pages components nuxt.config.js 2>/dev/null \| head[VERIFY-SEO-2] likely empty.

No structured data observed. For a B2B SaaS marketing surface, Organization, WebSite, Product schemas would help search engines.

5. Sitemap

Concern Status
sitemap.xml generated? No — no @nuxtjs/sitemap module in deps
static/sitemap.xml Not present (ls static/sitemap.xml → not found)
data/sitemap.js Yes — but this is the app navigation ("which menu item to highlight"), not a search-engine sitemap

Finding [WC-SEO-5] (Severity: Low): No sitemap.xml. For the public marketing pages, generate one — Phase 0 effort.

6. robots.txt

ls static/robots.txt dist/robots.txt 2>/dev/null → not found.

Finding [WC-SEO-6] (Severity: Low): No robots.txt. Add at minimum:

User-agent: *
Disallow: /<accid>/
Disallow: /redirect
Disallow: /callback
Disallow: /heartbeat
Sitemap: https://<host>/sitemap.xml

Phase 0 effort.

7. Canonical URLs

Not set. Per-page canonical URLs prevent duplicate-content penalties (e.g., when a marketing page is reachable at /?utm_source=x and /, the canonical resolves to /).

Finding [WC-SEO-7] (Severity: Low–Medium): Per-page canonical URLs not declared. Phase 0/1.

8. 404 handling

Nuxt 2's default behaviour for unmatched routes is to render layouts/error.vue with HTTP 404. [VERIFY-SEO-3] confirm by hitting an invalid URL on the deployed app.

If the server (nginx) is configured to return 200 for all routes (SPA fallback), then the 404 is HTTP-200 with error content — bad for SEO. [VERIFY-SEO-4] check nginx.conf for try_files or error_page directives.

nginx.conf was inspected: error_page 404 /404.html is set (nginx.conf:89-91), pointing to a /404.html static page that may or may not exist. The Nuxt SPA's own 404 (rendered client-side) is reachable at any unmatched path served as index.html (HTTP 200). So the actual behaviour depends on the request path.

[VERIFY-SEO-4] confirm: hitting /<accid>/nonexistent returns HTTP 200 (SPA shell, then client renders 404), while hitting /.well-known/something returns nginx's 404.html.

9. Marketing landing pages

The app contains several public marketing-ish surfaces:

  • pages/index.vue (/ — login + landing)
  • pages/plan/index.vue
  • pages/special_offer/index.vue
  • pages/corporate_offer/index.vue
  • pages/privacy/index.vue
  • pages/terms_and_conditions.vue
  • pages/fbDeletionStatus/index.vue (Facebook compliance)

These are the SEO-relevant pages. [VERIFY-SEO-5] check whether any have per-page head() blocks setting OG tags.

10. Analytics → SEO insights

GTM is configured. Google Search Console is not in the repo (it's a Search Console property, configured via DNS or meta tag verification). [VERIFY-SEO-6] is the marketing site verified in Search Console?

11. Recommendations

Phase 0a / Phase 0

  • Add static/robots.txt with sensible disallow rules.
  • Generate sitemap.xml for the public marketing pages.
  • Remove maximum-scale=1, user-scalable=0 from the viewport meta tag.
  • Add baseline global OG / Twitter Card tags in nuxt.config.js → head.
  • Wire middleware/head.js (or its title-setting logic) so per-page titles actually fire.
  • Add per-page head() blocks with title, description, canonical, OG image for the public marketing pages.

Phase 1

  • Add JSON-LD Organization and WebSite schemas to the marketing pages.
  • Move the marketing surface to a separate Nuxt 2 / Nuxt 3 SSR build (or to a dedicated Webflow / Framer / etc. site) to avoid the SPA-SEO penalty entirely. Most B2B SaaS companies ship marketing on a separate static site for this reason.

12. Open questions

Tracked in ./verify-markers.md:

  • [VERIFY-SEO-1] Page count with custom head() blocks
  • [VERIFY-SEO-2] JSON-LD presence
  • [VERIFY-SEO-3]/[VERIFY-SEO-4] 404 HTTP status behaviour
  • [VERIFY-SEO-5] OG tags on marketing pages
  • [VERIFY-SEO-6] Search Console verification