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:
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user