feat(seo): facts in src/data/business.ts, SEO + JSON-LD components in layout
- business.ts: single source of truth; unknown facts (city, area, prices, packages, phone, delivery time, experience, testimonials) are null and are skipped everywhere. faq.ts builds answers only from known facts. - portfolio-meta.ts: data hook for real per-image alt texts. - SEO.astro: title, description, canonical, Open Graph, Twitter, og:image (EXIF-free JPG via getImage, absolute URL). - SchemaOrg.astro: @graph with WebSite, Person, ProfessionalService (sameAs, ReserveAction to the customer portal, offer catalog without prices), WebPage, optional FAQPage and BreadcrumbList. - MainLayout: lang=sv, Swedish nav (adds Tjänster), consent banner and footer in Swedish, lyxlimo/karleksguiden line removed, info@ address. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WvE3C43Uwtv2vtnoFBLDw7
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
ce5676f552
commit
30e385ec7f
@@ -0,0 +1,46 @@
|
||||
---
|
||||
// src/components/SEO.astro
|
||||
// <title>, meta description, canonical, Open Graph och Twitter-kort för en sida.
|
||||
import { business } from '../data/business';
|
||||
import { normalizePath } from '../lib/url';
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
description: string;
|
||||
/** Sökväg utan avslutande snedstreck, t.ex. "/about". Annars härleds den från URL:en. */
|
||||
path?: string;
|
||||
/** Absolut URL till og:image (MainLayout tar fram en EXIF-fri JPG via getImage). */
|
||||
imageUrl: string;
|
||||
imageWidth?: number;
|
||||
imageHeight?: number;
|
||||
imageAlt: string;
|
||||
type?: 'website' | 'profile' | 'article';
|
||||
noindex?: boolean;
|
||||
}
|
||||
|
||||
const { title, description, path, imageUrl, imageWidth, imageHeight, imageAlt, type = 'website', noindex = false } = Astro.props;
|
||||
|
||||
const site = Astro.site ?? new URL(business.url);
|
||||
const canonical = new URL(normalizePath(path ?? Astro.url.pathname), site).toString();
|
||||
---
|
||||
|
||||
<title>{title}</title>
|
||||
<meta name="description" content={description} />
|
||||
{noindex ? <meta name="robots" content="noindex, follow" /> : <link rel="canonical" href={canonical} />}
|
||||
|
||||
<meta property="og:type" content={type} />
|
||||
<meta property="og:site_name" content={business.brand} />
|
||||
<meta property="og:locale" content="sv_SE" />
|
||||
<meta property="og:title" content={title} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:url" content={canonical} />
|
||||
<meta property="og:image" content={imageUrl} />
|
||||
{imageWidth && <meta property="og:image:width" content={String(imageWidth)} />}
|
||||
{imageHeight && <meta property="og:image:height" content={String(imageHeight)} />}
|
||||
<meta property="og:image:alt" content={imageAlt} />
|
||||
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:image" content={imageUrl} />
|
||||
<meta name="twitter:image:alt" content={imageAlt} />
|
||||
Reference in New Issue
Block a user