dana-medical-site/web/src/entry-server.jsx
2026-10-14 13:33:13 +00:00

102 lines
7.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom/server.js';
import App from './App.jsx';
import { ApiDataProvider } from './context/ApiData.jsx';
import { parseBlocks } from './lib/serviceContent.js';
import { serviceMetadata, doctorMetadata, priceMetadata, pageMetadata, jsonForHTML } from './lib/seo.js';
import { DIRECTION_MEDIA, directionOf } from './lib/media.js';
export { parseBlocks };
export function siteFromCommon(common) {
const branches = common.branches || [];
const settings = common.settings || {};
const first = branches[0];
const coords = branches.find((b) => b.lat && b.lon);
return {
phone: first?.phones?.[0] || '+7 928 962-00-94', hours: first?.work_hours?.[0] || 'Пн–Вс: 08:00–20:00',
addresses: branches.map((b) => b.address.replace(/^г\.\s*/, '').replace(/(\d)\(/g, '$1 (')),
branches, coords: coords ? { lat: coords.lat, lon: coords.lon } : null,
maxUrl: settings.max_url || '', email: settings.email || '', legal: settings.legal || null,
};
}
export function renderPage(url, data, siteURL) {
const pathname = url.split('?')[0];
const initial = { ...data, prerendered: true, siteURL };
const servicePath = pathname.startsWith('/services/') ? pathname.slice(10).replace(/\/$/, '') : '';
const service = initial[`service:${servicePath}`]?.data;
if (service) {
service.content_sections = parseBlocks(service.blocks);
const specialty = DIRECTION_MEDIA[directionOf(service.path)]?.specialty;
initial[`doctors:${specialty}`] = { data: (initial.doctors?.data || []).filter((d) => d.specialties?.some((s) => s.slug === specialty)), meta: {} };
}
const parts = pathname.split('/').filter(Boolean);
const doctor = parts.length === 4 && parts[0] === 'company' ? initial[`doctor:${parts[3]}`]?.data : null;
const legal = parts[0] === 'legaldocs' ? initial[`page:${parts[1] || 'legaldocs'}`]?.data : null;
const checkup = parts.length === 2 && parts[0] === 'checkup' ? initial[`checkup:${parts[1]}`]?.data : null;
const specialty = parts.length === 3 && parts[0] === 'company' ? initial.specialties?.data.find((s) => s.slug === parts[2]) : null;
const titles = { '/services/': 'Услуги', '/company/staff/': 'Врачи', '/price/': 'Цены на медицинские услуги',
'/contacts/': 'Контакты', '/checkup/': 'Чек-апы', '/sales/': 'Акции и специальные предложения',
'/company/': 'О клинике', '/news/': 'Новости', '/articles/': 'Статьи', '/reviews/': 'Отзывы',
'/company/partners/': 'Партнёры', '/company/nadzor/': 'Надзорные органы', '/company/vacancy/': 'Вакансии',
'/info/faq/': 'Вопросы и ответы', '/company/licenses/': 'Лицензии и награды', '/patients/': 'Пациенту' };
// раздел «Компании» → запись pages с заголовком и SEO
const sectionSlugs = { '/company/': 'about', '/news/': 'news', '/articles/': 'articles', '/reviews/': 'reviews',
'/company/partners/': 'partners', '/company/nadzor/': 'nadzor', '/company/vacancy/': 'vacancy', '/info/faq/': 'faq', '/company/licenses/': 'licenses', '/patients/': 'patients' };
const section = sectionSlugs[pathname] ? initial[`page:${sectionSlugs[pathname]}`]?.data : null;
const post = parts.length === 2 && ['news', 'articles'].includes(parts[0]) ? initial[`${parts[0]}:${parts[1]}`]?.data : null;
let metadata = pageMetadata(pathname, specialty?.name || (pathname.startsWith('/price/') ? 'Цены на медицинские услуги' : titles[pathname]) || 'Клиника семейного здоровья', siteURL);
if (service) metadata = serviceMetadata(service, siteURL);
if (doctor) metadata = doctorMetadata(doctor, siteURL);
if (legal) metadata = pageMetadata(pathname, legal.title, siteURL, legal.seo);
if (checkup) metadata = pageMetadata(pathname, checkup.name, siteURL, checkup.seo, checkup.lead);
if (section) metadata = pageMetadata(pathname, section.title, siteURL, section.seo);
if (post) metadata = pageMetadata(pathname, post.title, siteURL, post.seo, post.lead);
if (parts[0] === 'price') metadata = priceMetadata(parts[1], initial.prices?.data, siteURL);
const html = renderToString(<ApiDataProvider value={initial}>
<StaticRouter location={url}><App /></StaticRouter>
</ApiDataProvider>);
const name = doctor?.full_name || legal?.title || checkup?.name || post?.title || section?.title || specialty?.name || titles[pathname] || metadata.title;
const crumbs = [{ name: 'Главная', item: `${siteURL}/` }];
if (doctor) crumbs.push({ name: 'Врачи', item: `${siteURL}/company/staff/` });
if (post) crumbs.push({ name: titles[`/${parts[0]}/`], item: `${siteURL}/${parts[0]}/` });
if (pathname !== '/') crumbs.push({ name, item: metadata.canonical });
const clinics = (initial.site?.branches || []).map((b) => ({ '@type': 'MedicalClinic',
'@id': `${siteURL}/contacts/#${b.slug}`, name: `DANA Medical — ${b.name}`, url: `${siteURL}/contacts/`,
address: { '@type': 'PostalAddress', streetAddress: b.address, addressCountry: 'RU' }, telephone: b.phones?.[0],
}));
const schema = service ? null : { '@context': 'https://schema.org', '@graph': [
{ '@type': doctor || checkup ? 'MedicalWebPage' : post ? (parts[0] === 'news' ? 'WebPage' : 'MedicalWebPage') : 'WebPage', '@id': metadata.canonical,
name, url: metadata.canonical, description: metadata.description, inLanguage: 'ru-RU',
breadcrumb: { '@id': `${metadata.canonical}#breadcrumbs` },
...(doctor ? { mainEntity: { '@id': `${metadata.canonical}#physician` } } : {}),
},
{ '@type': 'BreadcrumbList', '@id': `${metadata.canonical}#breadcrumbs`, itemListElement: crumbs.map((c, i) => ({ '@type': 'ListItem', position: i + 1, ...c })) },
...clinics,
...(doctor ? [{ '@type': 'Physician', '@id': `${metadata.canonical}#physician`, name: doctor.full_name,
url: metadata.canonical, description: doctor.position || undefined,
image: doctor.photo ? new URL(doctor.photo, siteURL).href : undefined }] : []),
] };
return { html, initial: jsonForHTML(initial), metadata, schema };
}
export function renderService(service, parent, common, siteURL) {
service.content_sections = parseBlocks(service.blocks);
const specialty = DIRECTION_MEDIA[directionOf(service.path)]?.specialty;
const initial = {
prerendered: true, siteURL, site: common.site,
[`service:${service.path}`]: { data: service, meta: {} },
specialties: { data: common.specialties, meta: {} },
prices: { data: common.prices || [], meta: {} },
[`doctors:${specialty}`]: { data: common.doctors.filter((d) =>
d.specialties?.some((s) => s.slug === specialty)), meta: {} },
};
if (parent) initial[`service:${parent.path}`] = { data: parent, meta: {} };
const html = renderToString(
<ApiDataProvider value={initial}>
<StaticRouter location={`/services/${service.path}/`}><App /></StaticRouter>
</ApiDataProvider>,
);
return { html, initial: jsonForHTML(initial), metadata: serviceMetadata(service, siteURL) };
}