Consolidate Organization schema, add ItemList/Offer markup, expand FAQ
- Merge the duplicate Organization/LocalBusiness JSON-LD blocks (home's LocalBusiness + o-kompanii's Organization) into one shared getOrganizationSchema() call rendered on every page. Deliberately NOT wired through the root layout: fetches there get frozen into the static shell at build time (backend unreachable during image build) and never revalidate, silently serving stale data to crawlers - each page calls it directly instead, where revalidation is proven to work. - Add optional ItemList schema on /uslugi and Offer/OfferCatalog schema on /aktsii; entity pages (/uslugi/[slug] Service, /blog/[slug] Article) reference the organization via @id instead of duplicating it. - Add 8 more real FAQ entries (pricing model, contract terms, onboarding time, hybrid IT staff setup, supported stacks, backups, 152-FZ compliance, escalation) and give every FAQ its own slug and dedicated /faq/[slug] page with its own title/H1/FAQPage schema, modeled on the one-topic-per-page pattern (h/t remontka.pro). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013otXCiTZsxdZ4SJKZ9wUky
This commit is contained in:
1 parent
f53c8b1ed7
commit
1e4f97030a
19 files changed
+271
-69
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import Header from '../../components/Header.jsx';
|
||||
import Footer from '../../components/Footer.jsx';
|
||||
import Promotions from '../../components/sections/Promotions.jsx';
|
||||
import { ORGANIZATION_ID, getOrganizationSchema } from '../../lib/schema.js';
|
||||
import { serverApi } from '../../lib/serverApi.js';
|
||||
|
||||
export const metadata = {
|
||||
@@ -10,11 +11,38 @@ export const metadata = {
|
||||
alternates: { canonical: '/aktsii' },
|
||||
};
|
||||
|
||||
function parsePrice(priceText) {
|
||||
const digits = (priceText.match(/\d[\d\s ]*/) || [])[0];
|
||||
if (!digits) return null;
|
||||
const value = Number(digits.replace(/[\s ]/g, ''));
|
||||
return Number.isFinite(value) && value > 0 ? value : null;
|
||||
}
|
||||
|
||||
export default async function PromotionsPage() {
|
||||
const promotions = await serverApi.getPromotions();
|
||||
const [promotions, orgSchema] = await Promise.all([serverApi.getPromotions(), getOrganizationSchema()]);
|
||||
|
||||
const offersSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'OfferCatalog',
|
||||
name: 'Акции TopSysOps',
|
||||
itemListElement: promotions.map((p) => {
|
||||
const price = parsePrice(p.price_text);
|
||||
return {
|
||||
'@type': 'Offer',
|
||||
name: p.title,
|
||||
description: p.description,
|
||||
seller: { '@id': ORGANIZATION_ID },
|
||||
...(price ? { price, priceCurrency: 'RUB' } : {}),
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(orgSchema) }} />
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(offersSchema) }} />
|
||||
<Header />
|
||||
<Promotions promotions={promotions} showMailNote headingLevel="h1" />
|
||||
<Footer />
|
||||
|
||||
@@ -5,6 +5,7 @@ import Header from '../../../components/Header.jsx';
|
||||
import Footer from '../../../components/Footer.jsx';
|
||||
import Reveal from '../../../components/Reveal.jsx';
|
||||
import { serverApi } from '../../../lib/serverApi.js';
|
||||
import { ORGANIZATION_ID } from '../../../lib/schema.js';
|
||||
|
||||
function formatDate(iso) {
|
||||
if (!iso) return '';
|
||||
@@ -31,7 +32,7 @@ export default async function BlogPostPage({ params }) {
|
||||
headline: post.title,
|
||||
...(post.excerpt ? { description: post.excerpt } : {}),
|
||||
...(post.published_at ? { datePublished: post.published_at } : {}),
|
||||
author: { '@id': `${process.env.NEXT_PUBLIC_SITE_URL || 'https://top-sysops.ru'}/#organization` },
|
||||
author: { '@id': ORGANIZATION_ID },
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -5,6 +5,7 @@ import Footer from '../../components/Footer.jsx';
|
||||
import SectionHeading from '../../components/SectionHeading.jsx';
|
||||
import Reveal from '../../components/Reveal.jsx';
|
||||
import { serverApi } from '../../lib/serverApi.js';
|
||||
import { getOrganizationSchema } from '../../lib/schema.js';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Блог',
|
||||
@@ -18,10 +19,12 @@ function formatDate(iso) {
|
||||
}
|
||||
|
||||
export default async function BlogListPage() {
|
||||
const posts = await serverApi.getBlogPosts();
|
||||
const [posts, orgSchema] = await Promise.all([serverApi.getBlogPosts(), getOrganizationSchema()]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(orgSchema) }} />
|
||||
<Header />
|
||||
<section className="max-w-3xl mx-auto px-5 sm:px-8 py-20 min-h-[60vh]">
|
||||
<SectionHeading
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import Link from 'next/link';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { ArrowLeft, ArrowRight } from 'lucide-react';
|
||||
import Header from '../../../components/Header.jsx';
|
||||
import Footer from '../../../components/Footer.jsx';
|
||||
import Reveal from '../../../components/Reveal.jsx';
|
||||
import { serverApi } from '../../../lib/serverApi.js';
|
||||
import { getOrganizationSchema } from '../../../lib/schema.js';
|
||||
|
||||
export async function generateMetadata({ params }) {
|
||||
const faq = await serverApi.getFaq(params.slug);
|
||||
if (!faq) return { title: 'Вопрос не найден' };
|
||||
return {
|
||||
title: faq.question,
|
||||
description: faq.answer.length > 160 ? `${faq.answer.slice(0, 157)}...` : faq.answer,
|
||||
alternates: { canonical: `/faq/${params.slug}` },
|
||||
};
|
||||
}
|
||||
|
||||
export default async function FaqDetailPage({ params }) {
|
||||
const [faq, orgSchema] = await Promise.all([serverApi.getFaq(params.slug), getOrganizationSchema()]);
|
||||
if (!faq) notFound();
|
||||
|
||||
const faqSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'FAQPage',
|
||||
mainEntity: [
|
||||
{
|
||||
'@type': 'Question',
|
||||
name: faq.question,
|
||||
acceptedAnswer: { '@type': 'Answer', text: faq.answer },
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(orgSchema) }} />
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(faqSchema) }} />
|
||||
|
||||
<Header />
|
||||
<article className="max-w-2xl mx-auto px-5 sm:px-8 py-20 min-h-[60vh]">
|
||||
<Link href="/faq" className="text-sm text-emerald-500 font-medium inline-flex items-center gap-1 mb-8">
|
||||
<ArrowLeft className="h-3.5 w-3.5" /> Все вопросы
|
||||
</Link>
|
||||
|
||||
<Reveal>
|
||||
<p className="text-xs uppercase tracking-widest text-emerald-500 mb-4 font-mono">> faq --detail</p>
|
||||
<h1 className="text-2xl sm:text-3xl font-bold mb-6 font-display">{faq.question}</h1>
|
||||
<p className="text-base text-slate-700 dark:text-slate-300">{faq.answer}</p>
|
||||
</Reveal>
|
||||
|
||||
<Reveal delay={100} className="mt-10">
|
||||
<Link
|
||||
href="/kontakty"
|
||||
className="inline-flex items-center gap-2 px-5 py-3 rounded-md bg-emerald-500 text-white font-medium text-sm hover:bg-emerald-600 transition-colors"
|
||||
>
|
||||
Остались вопросы? Напишите нам <ArrowRight className="h-4 w-4" />
|
||||
</Link>
|
||||
</Reveal>
|
||||
</article>
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import Header from '../../components/Header.jsx';
|
||||
import Footer from '../../components/Footer.jsx';
|
||||
import Faq from '../../components/sections/Faq.jsx';
|
||||
import { serverApi } from '../../lib/serverApi.js';
|
||||
import { getOrganizationSchema } from '../../lib/schema.js';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Частые вопросы об IT-аутсорсинге и услугах',
|
||||
@@ -10,7 +11,7 @@ export const metadata = {
|
||||
};
|
||||
|
||||
export default async function FaqPage() {
|
||||
const faqs = await serverApi.getFaqs();
|
||||
const [faqs, orgSchema] = await Promise.all([serverApi.getFaqs(), getOrganizationSchema()]);
|
||||
|
||||
const faqSchema =
|
||||
faqs.length > 0
|
||||
@@ -27,6 +28,8 @@ export default async function FaqPage() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(orgSchema) }} />
|
||||
{faqSchema && (
|
||||
// eslint-disable-next-line react/no-danger
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(faqSchema) }} />
|
||||
|
||||
@@ -2,6 +2,7 @@ import Header from '../../components/Header.jsx';
|
||||
import Footer from '../../components/Footer.jsx';
|
||||
import Cases from '../../components/sections/Cases.jsx';
|
||||
import { serverApi } from '../../lib/serverApi.js';
|
||||
import { getOrganizationSchema } from '../../lib/schema.js';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Кейсы и портфолио — сетевая инфраструктура, ИИ, кибербезопасность',
|
||||
@@ -11,10 +12,12 @@ export const metadata = {
|
||||
};
|
||||
|
||||
export default async function CasesPage() {
|
||||
const cases = await serverApi.getCases();
|
||||
const [cases, orgSchema] = await Promise.all([serverApi.getCases(), getOrganizationSchema()]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(orgSchema) }} />
|
||||
<Header />
|
||||
<Cases cases={cases} showDisclaimer headingLevel="h1" />
|
||||
<Footer />
|
||||
|
||||
@@ -2,6 +2,7 @@ import Header from '../../components/Header.jsx';
|
||||
import Footer from '../../components/Footer.jsx';
|
||||
import Contact from '../../components/sections/Contact.jsx';
|
||||
import { serverApi } from '../../lib/serverApi.js';
|
||||
import { getOrganizationSchema } from '../../lib/schema.js';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Контакты',
|
||||
@@ -10,10 +11,12 @@ export const metadata = {
|
||||
};
|
||||
|
||||
export default async function ContactPage() {
|
||||
const contact = await serverApi.getContact();
|
||||
const [contact, orgSchema] = await Promise.all([serverApi.getContact(), getOrganizationSchema()]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(orgSchema) }} />
|
||||
<Header />
|
||||
<Contact contact={contact} headingLevel="h1" />
|
||||
<Footer />
|
||||
|
||||
@@ -2,7 +2,6 @@ import './globals.css';
|
||||
import { ThemeProvider } from '../components/ThemeProvider.jsx';
|
||||
import SupportChat from '../components/SupportChat.jsx';
|
||||
import VisitTracker from '../components/VisitTracker.jsx';
|
||||
import { getOrganizationSchema } from '../lib/schema.js';
|
||||
|
||||
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://top-sysops.ru';
|
||||
|
||||
@@ -28,14 +27,17 @@ const NO_FLASH_SCRIPT = `
|
||||
})();
|
||||
`;
|
||||
|
||||
// Organization/LocalBusiness JSON-LD намеренно НЕ рендерится здесь: root layout
|
||||
// в этом проекте частично запекается в статическую оболочку на этапе сборки
|
||||
// (когда бэкенд ещё недоступен), и любые fetch-данные отсюда попадают в HTML
|
||||
// устаревшими навсегда, даже с revalidate. Каждая страница вызывает
|
||||
// getOrganizationSchema() сама - там это надёжно ревалидируется на каждый запрос.
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="ru">
|
||||
<head>
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script dangerouslySetInnerHTML={{ __html: NO_FLASH_SCRIPT }} />
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(getOrganizationSchema()) }} />
|
||||
</head>
|
||||
<body className="bg-slate-50 dark:bg-slate-950 text-slate-900 dark:text-slate-100">
|
||||
<ThemeProvider>{children}</ThemeProvider>
|
||||
|
||||
@@ -4,7 +4,7 @@ import Header from '../../components/Header.jsx';
|
||||
import Footer from '../../components/Footer.jsx';
|
||||
import Reveal from '../../components/Reveal.jsx';
|
||||
import SectionHeading from '../../components/SectionHeading.jsx';
|
||||
import { serverApi } from '../../lib/serverApi.js';
|
||||
import { getOrganizationSchema } from '../../lib/schema.js';
|
||||
|
||||
export const metadata = {
|
||||
title: 'О компании',
|
||||
@@ -43,24 +43,12 @@ const DIFFERENTIATORS = [
|
||||
];
|
||||
|
||||
export default async function AboutPage() {
|
||||
const contact = await serverApi.getContact();
|
||||
|
||||
const orgSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Organization',
|
||||
'@id': `${process.env.NEXT_PUBLIC_SITE_URL || 'https://top-sysops.ru'}/#organization`,
|
||||
name: 'TopSysOps',
|
||||
url: process.env.NEXT_PUBLIC_SITE_URL || 'https://top-sysops.ru',
|
||||
foundingDate: '2023',
|
||||
numberOfEmployees: { '@type': 'QuantitativeValue', minValue: 20 },
|
||||
...(contact?.address ? { address: { '@type': 'PostalAddress', streetAddress: contact.address, addressCountry: 'RU' } } : {}),
|
||||
};
|
||||
const orgSchema = await getOrganizationSchema();
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(orgSchema) }} />
|
||||
|
||||
<Header />
|
||||
<section className="max-w-4xl mx-auto px-5 sm:px-8 pt-16 pb-10">
|
||||
<Reveal>
|
||||
|
||||
@@ -2,6 +2,7 @@ import Header from '../../components/Header.jsx';
|
||||
import Footer from '../../components/Footer.jsx';
|
||||
import Reviews from '../../components/sections/Reviews.jsx';
|
||||
import { serverApi } from '../../lib/serverApi.js';
|
||||
import { getOrganizationSchema } from '../../lib/schema.js';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Отзывы клиентов TopSysOps',
|
||||
@@ -10,10 +11,12 @@ export const metadata = {
|
||||
};
|
||||
|
||||
export default async function ReviewsPage() {
|
||||
const reviews = await serverApi.getReviews();
|
||||
const [reviews, orgSchema] = await Promise.all([serverApi.getReviews(), getOrganizationSchema()]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(orgSchema) }} />
|
||||
<Header />
|
||||
<Reviews reviews={reviews} headingLevel="h1" />
|
||||
<Footer />
|
||||
|
||||
@@ -12,59 +12,26 @@ import Contact from '../components/sections/Contact.jsx';
|
||||
import Reveal from '../components/Reveal.jsx';
|
||||
import SectionHeading from '../components/SectionHeading.jsx';
|
||||
import { serverApi } from '../lib/serverApi.js';
|
||||
import { getOrganizationSchema } from '../lib/schema.js';
|
||||
|
||||
export const metadata = {
|
||||
alternates: { canonical: '/' },
|
||||
};
|
||||
|
||||
export default async function HomePage() {
|
||||
const [services, promotions, reviews, cases, contact] = await Promise.all([
|
||||
const [services, promotions, reviews, cases, contact, orgSchema] = await Promise.all([
|
||||
serverApi.getServices(),
|
||||
serverApi.getPromotions(),
|
||||
serverApi.getReviews(),
|
||||
serverApi.getCases(),
|
||||
serverApi.getContact(),
|
||||
getOrganizationSchema(),
|
||||
]);
|
||||
|
||||
const business = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'LocalBusiness',
|
||||
'@id': `${process.env.NEXT_PUBLIC_SITE_URL || 'https://top-sysops.ru'}/#organization`,
|
||||
name: 'TopSysOps',
|
||||
url: process.env.NEXT_PUBLIC_SITE_URL || 'https://top-sysops.ru',
|
||||
...(contact?.address ? { address: { '@type': 'PostalAddress', streetAddress: contact.address, addressCountry: 'RU' } } : {}),
|
||||
...(contact?.phone ? { telephone: contact.phone } : {}),
|
||||
...(contact?.email ? { email: contact.email } : {}),
|
||||
...(reviews.length > 0
|
||||
? {
|
||||
aggregateRating: {
|
||||
'@type': 'AggregateRating',
|
||||
ratingValue: (reviews.reduce((sum, r) => sum + r.rating, 0) / reviews.length).toFixed(1),
|
||||
reviewCount: reviews.length,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(services.length > 0
|
||||
? {
|
||||
hasOfferCatalog: {
|
||||
'@type': 'OfferCatalog',
|
||||
name: 'Услуги TopSysOps',
|
||||
itemListElement: services.flatMap((cat) =>
|
||||
cat.items.map((it) => ({
|
||||
'@type': 'Offer',
|
||||
itemOffered: { '@type': 'Service', name: it.name, description: it.note || cat.description },
|
||||
}))
|
||||
),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(business) }} />
|
||||
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(orgSchema) }} />
|
||||
<Header />
|
||||
<Hero />
|
||||
<AccountPromo />
|
||||
|
||||
@@ -3,7 +3,11 @@ import { serverApi } from '../lib/serverApi.js';
|
||||
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://top-sysops.ru';
|
||||
|
||||
export default async function sitemap() {
|
||||
const [posts, services] = await Promise.all([serverApi.getBlogPosts(), serverApi.getServices()]);
|
||||
const [posts, services, faqs] = await Promise.all([
|
||||
serverApi.getBlogPosts(),
|
||||
serverApi.getServices(),
|
||||
serverApi.getFaqs(),
|
||||
]);
|
||||
|
||||
return [
|
||||
{ url: `${SITE_URL}/`, changeFrequency: 'weekly', priority: 1.0 },
|
||||
@@ -18,6 +22,11 @@ export default async function sitemap() {
|
||||
{ url: `${SITE_URL}/o-kompanii`, changeFrequency: 'monthly', priority: 0.6 },
|
||||
{ url: `${SITE_URL}/otzyvy`, changeFrequency: 'weekly', priority: 0.5 },
|
||||
{ url: `${SITE_URL}/faq`, changeFrequency: 'monthly', priority: 0.5 },
|
||||
...faqs.filter((f) => f.slug).map((f) => ({
|
||||
url: `${SITE_URL}/faq/${f.slug}`,
|
||||
changeFrequency: 'monthly',
|
||||
priority: 0.4,
|
||||
})),
|
||||
{ url: `${SITE_URL}/kontakty`, changeFrequency: 'monthly', priority: 0.6 },
|
||||
{ url: `${SITE_URL}/blog`, changeFrequency: 'weekly', priority: 0.7 },
|
||||
...posts.map((p) => ({
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ServicePriceList } from '../../../components/sections/Services.jsx';
|
||||
import { getIcon } from '../../../lib/icons.js';
|
||||
import { getServiceLandingCopy } from '../../../lib/serviceLandingCopy.js';
|
||||
import { serverApi } from '../../../lib/serverApi.js';
|
||||
import { ORGANIZATION_ID } from '../../../lib/schema.js';
|
||||
|
||||
async function findCategory(slug) {
|
||||
const services = await serverApi.getServices();
|
||||
@@ -37,7 +38,7 @@ export default async function ServiceLandingPage({ params }) {
|
||||
'@type': 'Service',
|
||||
name: copy.h1,
|
||||
description: copy.metaDescription,
|
||||
provider: { '@id': `${process.env.NEXT_PUBLIC_SITE_URL || 'https://top-sysops.ru'}/#organization` },
|
||||
provider: { '@id': ORGANIZATION_ID },
|
||||
areaServed: 'RU',
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ import Header from '../../components/Header.jsx';
|
||||
import Footer from '../../components/Footer.jsx';
|
||||
import Services from '../../components/sections/Services.jsx';
|
||||
import { serverApi } from '../../lib/serverApi.js';
|
||||
import { getOrganizationSchema } from '../../lib/schema.js';
|
||||
|
||||
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://top-sysops.ru';
|
||||
|
||||
export const metadata = {
|
||||
title: 'Услуги IT-аутсорсинга и техподдержки — прайс-лист',
|
||||
@@ -11,10 +14,25 @@ export const metadata = {
|
||||
};
|
||||
|
||||
export default async function ServicesPage() {
|
||||
const services = await serverApi.getServices();
|
||||
const [services, orgSchema] = await Promise.all([serverApi.getServices(), getOrganizationSchema()]);
|
||||
|
||||
const itemListSchema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'ItemList',
|
||||
itemListElement: services.map((cat, i) => ({
|
||||
'@type': 'ListItem',
|
||||
position: i + 1,
|
||||
name: cat.title,
|
||||
url: `${SITE_URL}/uslugi/${cat.slug}`,
|
||||
})),
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(orgSchema) }} />
|
||||
{/* eslint-disable-next-line react/no-danger */}
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(itemListSchema) }} />
|
||||
<Header />
|
||||
<Services categories={services} headingLevel="h1" />
|
||||
<Footer />
|
||||
|
||||
Reference in new issue
Block a user