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 pathWithoutLocale = Astro.url.pathname.replace(/^\/ru/, "") || "/";
const enUrl = `https://randify.pro${getRelativeLocaleUrl("en", pathWithoutLocale)}`; const enUrl = `https://randify.pro${getRelativeLocaleUrl("en", pathWithoutLocale)}`;
const ruUrl = `https://randify.pro${getRelativeLocaleUrl("ru", 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> <!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" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300..700&display=swap"
rel="stylesheet" rel="stylesheet"
/> />
<link rel="canonical" href={canonicalUrl} />
<link rel="alternate" hreflang="en" href={enUrl} /> <link rel="alternate" hreflang="en" href={enUrl} />
<link rel="alternate" hreflang="ru" href={ruUrl} /> <link rel="alternate" hreflang="ru" href={ruUrl} />
<meta name="verification" content="er9ndnv9ih7agmh8" /> <meta name="verification" content="er9ndnv9ih7agmh8" />
<script
type="application/ld+json"
set:html={JSON.stringify(webPageSchema)}
/>
<meta name="theme-color" content="#534AB7" /> <meta name="theme-color" content="#534AB7" />
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" /> <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<slot name="head" />
<title>{title}</title> <title>{title}</title>
<Analytics /> <Analytics />
<script> <script>
+44
View File
@@ -14,12 +14,56 @@ const { generator } = Astro.props;
const lang = (Astro.currentLocale as Lang) || "en"; const lang = (Astro.currentLocale as Lang) || "en";
const T = useT(lang); const T = useT(lang);
const isRu = lang === "ru"; 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 <BaseLayout
title={isRu ? generator.ruSeoTitle : generator.seoTitle} title={isRu ? generator.ruSeoTitle : generator.seoTitle}
description={isRu ? generator.ruSeoDescription : generator.seoDescription} 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"> <div class="max-w-2xl mx-auto px-4 py-12 sm:py-20">
<nav aria-label="Breadcrumb" class="mb-10"> <nav aria-label="Breadcrumb" class="mb-10">
<a <a
+14
View File
@@ -8,9 +8,23 @@ import type { Lang } from "@/i18n/translations";
const lang = (Astro.currentLocale as Lang) || "en"; const lang = (Astro.currentLocale as Lang) || "en";
const T = useT(lang); 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> <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"> <div class="max-w-4xl mx-auto px-4 py-12 sm:py-20">
<header class="mb-12"> <header class="mb-12">
<div class="flex items-center gap-2 mb-6"> <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 lang = (Astro.currentLocale as Lang) || "en";
const T = useT(lang); 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> <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"> <div class="max-w-4xl mx-auto px-4 py-12 sm:py-20">
<header class="mb-12"> <header class="mb-12">
<div class="flex items-center gap-2 mb-6"> <div class="flex items-center gap-2 mb-6">