SEO & Metadata¶
Status: Audit v0.1 (2026-05-09).
1. Rendering model¶
| Property | Value | Source |
|---|---|---|
| Rendering | SPA — ssr: 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
canonicalURL at global or per-page level. maximum-scale=1, user-scalable=0in the viewport — prevents zoom, an accessibility issue. Removingmaximum-scaleanduser-scalableallows 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.vuepages/special_offer/index.vuepages/corporate_offer/index.vuepages/privacy/index.vuepages/terms_and_conditions.vuepages/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.txtwith sensible disallow rules. - Generate
sitemap.xmlfor the public marketing pages. - Remove
maximum-scale=1, user-scalable=0from 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
OrganizationandWebSiteschemas 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