Files
Randify.pro/src/layouts/GeneratorLayout.astro
T
emil 8de77203aa 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
2026-05-10 21:19:39 +03:00

125 lines
3.4 KiB
Plaintext

---
import BaseLayout from "./BaseLayout.astro";
import AdBanner from "../components/AdBanner.astro";
import SeoBlock from "../components/SeoBlock.astro";
import { useT } from "../i18n/translations";
import type { Lang } from "../i18n/translations";
import type { Generator } from "../data/generators";
interface Props {
generator: Generator;
}
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
href={isRu ? "/ru/" : "/"}
class="inline-flex items-center gap-1.5 text-sm text-zinc-500 hover:text-zinc-200 transition-colors focus:outline-none focus-visible:ring-2 focus-visible:ring-[#534AB7] rounded"
aria-label={T.backToAll}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path d="m15 18-6-6 6-6"></path>
</svg>
{T.backToAll}
</a>
</nav>
<header class="mb-2">
<div class="flex items-center gap-2 mb-4">
<span
class="inline-block w-2.5 h-2.5 rounded-full bg-[#534AB7]"
aria-hidden="true"></span>
<span
class="text-sm font-medium text-zinc-400 uppercase tracking-widest"
>randify</span
>
</div>
<h1 class="text-3xl sm:text-4xl font-bold text-zinc-100">
{isRu ? generator.ruPageTitle : generator.pageTitle}
</h1>
<p class="mt-2 text-base text-zinc-400">
{isRu ? generator.ruDescription : generator.description}
</p>
</header>
<main>
<slot />
</main>
<div class="mt-12 flex justify-center">
<AdBanner size="tile" />
</div>
<SeoBlock
lang={lang}
howTo={isRu ? generator.ruHowTo : generator.howTo}
whenTo={isRu ? generator.ruWhenTo : generator.whenTo}
/>
</div>
</BaseLayout>