feat: add JSON-LD structured data

- BaseLayout: WebPage schema on every page + canonical URL
- Homepages: WebSite schema
- GeneratorLayout: SoftwareApplication + BreadcrumbList schemas
- Add <slot name='head'> to BaseLayout for child-injected head content
This commit is contained in:
emil
2026-05-10 21:19:39 +03:00
parent 8421441664
commit 8de77203aa
4 changed files with 88 additions and 0 deletions
+16
View File
@@ -18,6 +18,16 @@ const { title = T.defaultTitle, description = T.defaultDesc } = Astro.props;
const pathWithoutLocale = Astro.url.pathname.replace(/^\/ru/, "") || "/";
const enUrl = `https://randify.pro${getRelativeLocaleUrl("en", pathWithoutLocale)}`;
const ruUrl = `https://randify.pro${getRelativeLocaleUrl("ru", pathWithoutLocale)}`;
const canonicalUrl = `https://randify.pro${Astro.url.pathname}`;
const webPageSchema = {
"@context": "https://schema.org",
"@type": "WebPage",
name: title,
description: description,
url: canonicalUrl,
inLanguage: lang,
};
---
<!doctype html>
@@ -33,12 +43,18 @@ const ruUrl = `https://randify.pro${getRelativeLocaleUrl("ru", pathWithoutLocale
href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap"
rel="stylesheet"
/>
<link rel="canonical" href={canonicalUrl} />
<link rel="alternate" hreflang="en" href={enUrl} />
<link rel="alternate" hreflang="ru" href={ruUrl} />
<meta name="verification" content="er9ndnv9ih7agmh8" />
<script
type="application/ld+json"
set:html={JSON.stringify(webPageSchema)}
/>
<meta name="theme-color" content="#534AB7" />
<link rel="manifest" href="/manifest.json" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<slot name="head" />
<title>{title}</title>
<Analytics />
<script>
+44
View File
@@ -14,12 +14,56 @@ const { generator } = Astro.props;
const lang = (Astro.currentLocale as Lang) || "en";
const T = useT(lang);
const isRu = lang === "ru";
const canonicalUrl = `https://randify.pro${Astro.url.pathname}`;
const appSchema = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
name: isRu ? generator.ruTitle : generator.title,
applicationCategory: "UtilityApplication",
operatingSystem: "Any",
offers: {
"@type": "Offer",
price: "0",
priceCurrency: "USD",
},
inLanguage: ["en", "ru"],
description: isRu ? generator.ruDescription : generator.description,
url: canonicalUrl,
};
const breadcrumbSchema = {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{
"@type": "ListItem",
position: 1,
name: "Randify.pro",
item: "https://randify.pro/",
},
{
"@type": "ListItem",
position: 2,
name: isRu ? generator.ruTitle : generator.title,
item: canonicalUrl,
},
],
};
---
<BaseLayout
title={isRu ? generator.ruSeoTitle : generator.seoTitle}
description={isRu ? generator.ruSeoDescription : generator.seoDescription}
>
<Fragment slot="head">
<script type="application/ld+json" set:html={JSON.stringify(appSchema)} />
<script
type="application/ld+json"
set:html={JSON.stringify(breadcrumbSchema)}
/>
</Fragment>
<div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
<nav aria-label="Breadcrumb" class="mb-10">
<a
+14
View File
@@ -8,9 +8,23 @@ import type { Lang } from "@/i18n/translations";
const lang = (Astro.currentLocale as Lang) || "en";
const T = useT(lang);
const webSiteSchema = {
"@context": "https://schema.org",
"@type": "WebSite",
name: "Randify.pro",
url: "https://randify.pro/",
description: T.defaultDesc,
inLanguage: lang,
};
---
<BaseLayout>
<script
type="application/ld+json"
set:html={JSON.stringify(webSiteSchema)}
slot="head"
/>
<div class="max-w-4xl mx-auto px-4 py-12 sm:py-20">
<header class="mb-12">
<div class="flex items-center gap-2 mb-6">
+14
View File
@@ -8,9 +8,23 @@ import type { Lang } from "@/i18n/translations";
const lang = (Astro.currentLocale as Lang) || "en";
const T = useT(lang);
const webSiteSchema = {
"@context": "https://schema.org",
"@type": "WebSite",
name: "Randify.pro",
url: "https://randify.pro/",
description: T.defaultDesc,
inLanguage: lang,
};
---
<BaseLayout>
<script
type="application/ld+json"
set:html={JSON.stringify(webSiteSchema)}
slot="head"
/>
<div class="max-w-4xl mx-auto px-4 py-12 sm:py-20">
<header class="mb-12">
<div class="flex items-center gap-2 mb-6">