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( ); 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( , ); return { html, initial: jsonForHTML(initial), metadata: serviceMetadata(service, siteURL) }; }