bilhej/frontend/src/composables/useSeo.ts
Hermes Agent f1fa539495
Some checks failed
CI / Lint, type check, unit tests, coverage (push) Failing after 26m9s
CI / E2E browser tests (push) Failing after 13m8s
SEO: comprehensive search engine optimization for bilhej.se
Adds 7 SEO improvements across the full stack:

1. Enhanced index.html:
   - Open Graph tags (og:title, og:description, og:image, og:locale sv_SE)
   - Twitter Card tags (summary_large_image)
   - Canonical URL (https://bilhej.se/)
   - Meta keywords, author, robots, language tags
   - Structured data in JSON-LD (Organization, WebSite, Service, FAQPage)
   - FAQ structured data with 4 Q&A pairs (pris, anonymitet, hur funkar det)
   - Preconnect hints for analytics origin

2. robots.txt:
   - Disallow: auth pages, admin, orders, payment, guest pages
   - Allow: public pages (/, /om-oss, /kontakt, /integritetspolicy, /villkor)
   - Crawl-delay: 10s
   - Points to sitemap

3. sitemap.xml:
   - 6 public URLs with proper changefreq and priority
   - Home (1.0), Om (0.7), Kontakt (0.6), GDPR/Villkor (0.5)

4. useSeo composable:
   - Dynamic document.title, meta description/OG/Twitter on route change
   - Canonical URL updates per page
   - Reset fallback for unnamed routes

5. Route-level SEO data:
   - 16 routes with unique title, description, keywords
   - Covers all public pages + key auth pages

6. App.vue: wired useSeo with route.name watcher (immediate: true)

7. nginx.conf:
   - Security headers: X-Frame-Options DENY, X-Content-Type-Options,
     X-XSS-Protection, Referrer-Policy, Permissions-Policy, HSTS
   - Far-future cache (1y, immutable) for static assets
   - No-cache for robots.txt and sitemap.xml
   - Deny access to dotfiles
   - Stronger gzip config
2026-07-18 17:54:13 +00:00

95 lines
2.6 KiB
TypeScript

/**
* useSeo — dynamically updates document <title> and meta tags on route change.
*
* Usage:
* const seo = useSeo()
* // On page mount / watch:
* watch(route, () => {
* seo.set({ title: 'Min sida', description: '...' })
* })
*
* The composable sets <title>, meta[name=description], meta[name=keywords],
* meta[property=og:title], meta[property=og:description], meta[name=twitter:title],
* and meta[name=twitter:description].
*/
export interface SeoMeta {
title: string
description: string
keywords?: string
ogTitle?: string
ogDescription?: string
canonical?: string
}
export function useSeo() {
function set(meta: SeoMeta) {
const title = meta.title ?? 'Bilhej'
const description =
meta.description ??
'Skicka brev till fordonsägare via registreringsnummer.'
// Document title
document.title = title
// Common meta tags
updateMeta('description', description)
if (meta.keywords) updateMeta('keywords', meta.keywords)
// Open Graph
updateMeta('og:title', meta.ogTitle ?? title, 'property')
updateMeta('og:description', meta.ogDescription ?? description, 'property')
// Twitter
updateMeta('twitter:title', meta.ogTitle ?? title, 'name')
updateMeta('twitter:description', meta.ogDescription ?? description, 'name')
// Canonical URL
updateCanonical(meta.canonical)
}
function reset() {
document.title = 'Bilhej — Skicka brev till fordonsägare'
updateMeta(
'description',
'Skicka ett fysiskt brev till en fordonsägare via registreringsnummer.',
)
updateMeta('og:title', 'Bilhej — Skicka brev till fordonsägare', 'property')
updateMeta(
'og:description',
'Skicka ett fysiskt brev till en fordonsägare via registreringsnummer.',
'property',
)
updateCanonical('https://bilhej.se/')
}
return { set, reset }
}
function updateMeta(
nameOrProperty: string,
content: string,
attr: 'name' | 'property' = 'name',
) {
let el = document.querySelector(
`meta[${attr}="${nameOrProperty}"]`,
) as HTMLMetaElement | null
if (!el) {
el = document.createElement('meta')
el.setAttribute(attr, nameOrProperty)
document.head.appendChild(el)
}
el.setAttribute('content', content)
}
function updateCanonical(href: string | undefined) {
let link = document.querySelector(
'link[rel="canonical"]',
) as HTMLLinkElement | null
if (!link) {
link = document.createElement('link')
link.setAttribute('rel', 'canonical')
document.head.appendChild(link)
}
link.setAttribute('href', href ?? 'https://bilhej.se/')
}