SEO: JSON-LD, hreflang x-default, 404 page, localized OG

- FaqSection: h1/h2 based on standalone prop (fixes missing H1 on /faq)
- faq/page: FAQPage JSON-LD, hreflang x-default, OG image/url
- kontakty/page: LocalBusiness JSON-LD (address, geo, tel, openingHours), hreflang x-default, OG
- uslugi/page: geo in H1 («в Пушкино»), hreflang x-default, OG, expanded description
- [lang]/page: Organization JSON-LD, localized OG (ru_RU/en_US), x-default hreflang
- [slug]/page: localized og:title/siteName, x-default hreflang, areaServed+telephone in Service JSON-LD
- not-found.tsx: 404 page → generates 404.html for nginx error_page directive

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-16 00:56:47 +03:00
parent e223f51bd9
commit 55f88d5227
7 changed files with 200 additions and 18 deletions

View File

@@ -9,10 +9,28 @@ export function generateStaticParams() {
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise<Metadata> { export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise<Metadata> {
const { lang } = await params const { lang } = await params
const d = getDictionary(lang) const d = getDictionary(lang)
const isRu = lang === 'ru'
return { return {
title: d.faq.title, title: d.faq.title,
description: d.faq.subtitle, description: isRu
alternates: { canonical: `https://sag24.ru/${lang}/faq/` }, ? 'Ответы на частые вопросы об IT-аутсорсинге в Пушкино: стоимость, сроки реакции, состав услуг, зона обслуживания.'
: 'Answers to common questions about IT outsourcing in Pushkino: pricing, response time, service scope, coverage area.',
alternates: {
canonical: `https://sag24.ru/${lang}/faq/`,
languages: {
'ru': 'https://sag24.ru/ru/faq/',
'en': 'https://sag24.ru/en/faq/',
'x-default': 'https://sag24.ru/ru/faq/',
},
},
openGraph: {
title: isRu ? 'Частые вопросы об IT-аутсорсинге | Сисадмингрупп' : 'IT Outsourcing FAQ | SysadminGroup',
description: isRu
? 'Ответы на частые вопросы об IT-аутсорсинге в Пушкино: стоимость, сроки, состав услуг.'
: 'Common IT outsourcing questions: pricing, SLA, service scope.',
url: `https://sag24.ru/${lang}/faq/`,
images: [{ url: '/og-image.png', width: 1200, height: 630 }],
},
} }
} }
@@ -20,9 +38,24 @@ export default async function FaqPage({ params }: { params: Promise<{ lang: stri
const { lang: langStr } = await params const { lang: langStr } = await params
const lang = langStr as Locale const lang = langStr as Locale
const d = getDictionary(lang) const d = getDictionary(lang)
const faqSchema = {
'@context': 'https://schema.org',
'@type': 'FAQPage',
'mainEntity': d.faq.items.map(item => ({
'@type': 'Question',
'name': item.q,
'acceptedAnswer': { '@type': 'Answer', 'text': item.a },
})),
}
return ( return (
<div className="pt-16"> <div className="pt-16">
<FaqSection d={d} standalone /> <FaqSection d={d} standalone />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(faqSchema) }}
/>
</div> </div>
) )
} }

View File

@@ -9,14 +9,64 @@ export function generateStaticParams() {
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise<Metadata> { export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise<Metadata> {
const { lang } = await params const { lang } = await params
const d = getDictionary(lang) const isRu = lang === 'ru'
return { return {
title: d.contact.title, title: isRu ? 'Контакты IT-компании в Пушкино' : 'Contact IT Company in Pushkino',
description: d.contact.subtitle, description: isRu
alternates: { canonical: `https://sag24.ru/${lang}/kontakty/` }, ? 'Телефоны, email и адрес Сисадмингрупп. IT-аутсорсинг и техподдержка в Пушкино и Московской области. Звоните: +7 495 363-74-76.'
: 'Phone, email and address of SysadminGroup. IT outsourcing and support in Pushkino and Moscow Region. Call: +7 495 363-74-76.',
alternates: {
canonical: `https://sag24.ru/${lang}/kontakty/`,
languages: {
'ru': 'https://sag24.ru/ru/kontakty/',
'en': 'https://sag24.ru/en/kontakty/',
'x-default': 'https://sag24.ru/ru/kontakty/',
},
},
openGraph: {
title: isRu ? 'Контакты | Сисадмингрупп' : 'Contact | SysadminGroup',
description: isRu
? 'IT-аутсорсинг в Пушкино. Телефон: +7 495 363-74-76. Московская область, Пушкино.'
: 'IT outsourcing in Pushkino. Phone: +7 495 363-74-76.',
url: `https://sag24.ru/${lang}/kontakty/`,
images: [{ url: '/og-image.png', width: 1200, height: 630 }],
},
} }
} }
const localBusinessSchema = {
'@context': 'https://schema.org',
'@type': 'LocalBusiness',
'name': 'Сисадмингрупп',
'alternateName': 'ООО «Сисадмингрупп»',
'url': 'https://sag24.ru',
'logo': 'https://sag24.ru/logo.png',
'image': 'https://sag24.ru/og-image.png',
'telephone': ['+74953637476', '+74953637335', '+79099454456'],
'email': 'info@sag24.ru',
'address': {
'@type': 'PostalAddress',
'streetAddress': 'пр-кт Московский, д. 38/14',
'addressLocality': 'Пушкино',
'addressRegion': 'Московская область',
'postalCode': '141207',
'addressCountry': 'RU',
},
'geo': {
'@type': 'GeoCoordinates',
'latitude': 56.0094,
'longitude': 37.8572,
},
'areaServed': {
'@type': 'State',
'name': 'Московская область',
},
'serviceType': 'IT-аутсорсинг',
'priceRange': 'от 15 000 руб./мес.',
'openingHours': 'Mo-Fr 09:00-18:00',
'sameAs': ['https://sag24.ru'],
}
export default async function KontaktyPage({ params }: { params: Promise<{ lang: string }> }) { export default async function KontaktyPage({ params }: { params: Promise<{ lang: string }> }) {
const { lang: langStr } = await params const { lang: langStr } = await params
const lang = langStr as Locale const lang = langStr as Locale
@@ -78,6 +128,10 @@ export default async function KontaktyPage({ params }: { params: Promise<{ lang:
</div> </div>
</div> </div>
</section> </section>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(localBusinessSchema) }}
/>
</div> </div>
) )
} }

View File

@@ -13,24 +13,52 @@ export function generateStaticParams() {
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise<Metadata> { export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise<Metadata> {
const { lang } = await params const { lang } = await params
const isRu = lang === 'ru'
return { return {
title: lang === 'ru' ? 'Сисадмингрупп — IT-аутсорсинг в Пушкино' : 'SysadminGroup — IT Outsourcing in Pushkino', title: isRu ? 'Сисадмингрупп — IT-аутсорсинг в Пушкино' : 'SysadminGroup — IT Outsourcing in Pushkino',
description: lang === 'ru' description: isRu
? 'IT-аутсорсинг, техническая поддержка, кибербезопасность для бизнеса в Пушкино и Московской области. Опыт 10+ лет, 150+ клиентов, SLA от 15 минут.' ? 'IT-аутсорсинг, техническая поддержка, кибербезопасность для бизнеса в Пушкино и Московской области. Опыт 10+ лет, 150+ клиентов, SLA от 15 минут.'
: 'IT outsourcing, technical support, cybersecurity for businesses in Pushkino and Moscow Region. 10+ years experience, 150+ clients, SLA from 15 min.', : 'IT outsourcing, technical support, cybersecurity for businesses in Pushkino and Moscow Region. 10+ years experience, 150+ clients, SLA from 15 min.',
alternates: { alternates: {
canonical: `https://sag24.ru/${lang}/`, canonical: `https://sag24.ru/${lang}/`,
languages: { 'ru': 'https://sag24.ru/ru/', 'en': 'https://sag24.ru/en/' }, languages: {
'ru': 'https://sag24.ru/ru/',
'en': 'https://sag24.ru/en/',
'x-default': 'https://sag24.ru/ru/',
},
}, },
openGraph: { openGraph: {
title: 'Сисадмингрупп — IT-аутсорсинг', title: isRu ? 'Сисадмингрупп — IT-аутсорсинг в Пушкино' : 'SysadminGroup — IT Outsourcing in Pushkino',
description: 'IT-поддержка для бизнеса в Пушкино', description: isRu
? 'IT-аутсорсинг, техподдержка, кибербезопасность для бизнеса в Пушкино и Московской области. SLA от 15 минут, 150+ клиентов, опыт 10+ лет.'
: 'IT outsourcing, tech support, cybersecurity for businesses in Pushkino and Moscow Region. SLA from 15 min, 150+ clients, 10+ years.',
url: `https://sag24.ru/${lang}/`, url: `https://sag24.ru/${lang}/`,
images: [{ url: '/og-image.png', width: 1200, height: 630 }], images: [{ url: '/og-image.png', width: 1200, height: 630 }],
}, },
} }
} }
const organizationSchema = {
'@context': 'https://schema.org',
'@type': 'Organization',
'name': 'Сисадмингрупп',
'alternateName': 'ООО «Сисадмингрупп»',
'url': 'https://sag24.ru',
'logo': 'https://sag24.ru/logo.png',
'telephone': ['+74953637476', '+74953637335', '+79099454456'],
'email': 'info@sag24.ru',
'address': {
'@type': 'PostalAddress',
'streetAddress': 'пр-кт Московский, д. 38/14',
'addressLocality': 'Пушкино',
'addressRegion': 'Московская область',
'postalCode': '141207',
'addressCountry': 'RU',
},
'areaServed': 'Московская область',
'sameAs': ['https://sag24.ru'],
}
export default async function HomePage({ params }: { params: Promise<{ lang: string }> }) { export default async function HomePage({ params }: { params: Promise<{ lang: string }> }) {
const { lang: langStr } = await params const { lang: langStr } = await params
const lang = langStr as Locale const lang = langStr as Locale
@@ -43,6 +71,10 @@ export default async function HomePage({ params }: { params: Promise<{ lang: str
<ClientsSection d={d} /> <ClientsSection d={d} />
<PartnersSection d={d} /> <PartnersSection d={d} />
<FaqSection d={d} /> <FaqSection d={d} />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(organizationSchema) }}
/>
</> </>
) )
} }

View File

@@ -18,6 +18,7 @@ export async function generateMetadata({ params }: { params: Promise<{ lang: str
const d = getDictionary(lang) const d = getDictionary(lang)
const svc = d.services.items.find(s => s.slug === slug) const svc = d.services.items.find(s => s.slug === slug)
if (!svc) return {} if (!svc) return {}
const orgName = lang === 'ru' ? 'Сисадмингрупп' : 'SysadminGroup'
return { return {
title: svc.title, title: svc.title,
description: svc.description, description: svc.description,
@@ -26,11 +27,15 @@ export async function generateMetadata({ params }: { params: Promise<{ lang: str
languages: { languages: {
'ru': `https://sag24.ru/ru/uslugi/${slug}/`, 'ru': `https://sag24.ru/ru/uslugi/${slug}/`,
'en': `https://sag24.ru/en/uslugi/${slug}/`, 'en': `https://sag24.ru/en/uslugi/${slug}/`,
'x-default': `https://sag24.ru/ru/uslugi/${slug}/`,
}, },
}, },
openGraph: { openGraph: {
title: `${svc.title} | Сисадмингрупп`, title: `${svc.title} | ${orgName}`,
description: svc.description, description: svc.description,
url: `https://sag24.ru/${lang}/uslugi/${slug}/`,
siteName: orgName,
locale: lang === 'ru' ? 'ru_RU' : 'en_US',
images: [{ url: '/og-image.png' }], images: [{ url: '/og-image.png' }],
}, },
} }
@@ -112,10 +117,13 @@ export default async function ServicePage({ params }: { params: Promise<{ lang:
'@type': 'Service', '@type': 'Service',
'name': svc.title, 'name': svc.title,
'description': svc.description, 'description': svc.description,
'url': `https://sag24.ru/${lang}/uslugi/${slug}/`,
'areaServed': lang === 'ru' ? 'Московская область' : 'Moscow Region',
'provider': { 'provider': {
'@type': 'Organization', '@type': 'Organization',
'name': 'Сисадмингрупп', 'name': lang === 'ru' ? 'Сисадмингрупп' : 'SysadminGroup',
'url': 'https://sag24.ru', 'url': 'https://sag24.ru',
'telephone': '+7-495-363-74-76',
}, },
}), }),
}} }}

View File

@@ -9,10 +9,28 @@ export function generateStaticParams() {
export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise<Metadata> { export async function generateMetadata({ params }: { params: Promise<{ lang: string }> }): Promise<Metadata> {
const { lang } = await params const { lang } = await params
const isRu = lang === 'ru'
return { return {
title: lang === 'ru' ? 'Услуги IT-аутсорсинга' : 'IT Outsourcing Services', title: isRu ? 'Услуги IT-аутсорсинга в Пушкино' : 'IT Outsourcing Services in Pushkino',
description: lang === 'ru' ? 'Полный спектр IT-услуг для бизнеса: аутсорсинг, безопасность, поддержка, сети, серверы.' : 'Full range of IT services for business.', description: isRu
alternates: { canonical: `https://sag24.ru/${lang}/uslugi/` }, ? 'IT-аутсорсинг, кибербезопасность, техподдержка, видеонаблюдение, сети, серверы, телефония и облако. Комплексное IT-обслуживание бизнеса в Пушкино и МО.'
: 'IT outsourcing, cybersecurity, technical support, video surveillance, networks, servers, telephony and cloud. Full IT service in Pushkino and Moscow Region.',
alternates: {
canonical: `https://sag24.ru/${lang}/uslugi/`,
languages: {
'ru': 'https://sag24.ru/ru/uslugi/',
'en': 'https://sag24.ru/en/uslugi/',
'x-default': 'https://sag24.ru/ru/uslugi/',
},
},
openGraph: {
title: isRu ? 'Услуги IT-аутсорсинга | Сисадмингрупп' : 'IT Services | SysadminGroup',
description: isRu
? 'Полный спектр IT-услуг для бизнеса в Пушкино и Московской области.'
: 'Full range of IT services for businesses in Pushkino and Moscow Region.',
url: `https://sag24.ru/${lang}/uslugi/`,
images: [{ url: '/og-image.png', width: 1200, height: 630 }],
},
} }
} }
@@ -27,7 +45,9 @@ export default async function UslugiPage({ params }: { params: Promise<{ lang: s
<section className="py-24 bg-slate-50"> <section className="py-24 bg-slate-50">
<div className="max-w-6xl mx-auto px-4 sm:px-6"> <div className="max-w-6xl mx-auto px-4 sm:px-6">
<div className="text-center mb-16"> <div className="text-center mb-16">
<h1 className="text-4xl sm:text-5xl font-bold text-slate-900 mb-4">{d.services.title}</h1> <h1 className="text-4xl sm:text-5xl font-bold text-slate-900 mb-4">
{lang === 'ru' ? 'Услуги IT-аутсорсинга в Пушкино' : 'IT Outsourcing Services in Pushkino'}
</h1>
<p className="text-slate-500 text-lg max-w-2xl mx-auto">{d.services.subtitle}</p> <p className="text-slate-500 text-lg max-w-2xl mx-auto">{d.services.subtitle}</p>
</div> </div>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6"> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">

32
src/app/not-found.tsx Normal file
View File

@@ -0,0 +1,32 @@
import type { Metadata } from 'next'
import Link from 'next/link'
export const metadata: Metadata = {
title: 'Страница не найдена — Сисадмингрупп',
description: 'Запрошенная страница не существует.',
robots: { index: false },
}
export default function NotFound() {
return (
<html lang="ru">
<body style={{ margin: 0, fontFamily: 'sans-serif', background: '#f8fafc' }}>
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '2rem' }}>
<div style={{ textAlign: 'center', maxWidth: '480px' }}>
<div style={{ fontSize: '6rem', fontWeight: 700, color: '#2563eb', lineHeight: 1 }}>404</div>
<h1 style={{ fontSize: '1.5rem', fontWeight: 700, color: '#0f172a', margin: '1rem 0 0.5rem' }}>
Страница не найдена
</h1>
<p style={{ color: '#64748b', marginBottom: '2rem' }}>
Запрошенная страница не существует или была перемещена.
</p>
<Link href="/ru/"
style={{ display: 'inline-block', padding: '0.75rem 2rem', background: '#2563eb', color: '#fff', borderRadius: '0.5rem', textDecoration: 'none', fontWeight: 600 }}>
На главную
</Link>
</div>
</div>
</body>
</html>
)
}

View File

@@ -24,7 +24,10 @@ export default function FaqSection({ d, standalone }: { d: Dictionary; standalon
<section id="faq" className={`${standalone ? 'py-24' : 'py-24 border-t border-slate-100'} bg-white`}> <section id="faq" className={`${standalone ? 'py-24' : 'py-24 border-t border-slate-100'} bg-white`}>
<div className="max-w-3xl mx-auto px-4 sm:px-6"> <div className="max-w-3xl mx-auto px-4 sm:px-6">
<div className="text-center mb-12"> <div className="text-center mb-12">
<h2 className="text-3xl sm:text-4xl font-bold text-slate-900 mb-4">{d.faq.title}</h2> {standalone
? <h1 className="text-3xl sm:text-4xl font-bold text-slate-900 mb-4">{d.faq.title}</h1>
: <h2 className="text-3xl sm:text-4xl font-bold text-slate-900 mb-4">{d.faq.title}</h2>
}
<p className="text-slate-500 text-lg">{d.faq.subtitle}</p> <p className="text-slate-500 text-lg">{d.faq.subtitle}</p>
</div> </div>
<div className="flex flex-col gap-3"> <div className="flex flex-col gap-3">