import { renderToString } from 'react-dom/server'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import type { Locale, PublicConfig, VoiceAssignment } from '@wellformed/shared'; import { MarketingScreen } from './components/MarketingScreen'; import { I18nProvider } from './i18n'; import { messages } from './i18n/messages'; import { SessionProvider } from './state/session'; import { ToastProvider } from './state/toasts'; /** * The marketing page as a string, for the server to hand to whoever has no * JavaScript yet: crawlers on the first wave, and every visitor for the first * few hundred milliseconds. * * This renders the same component the browser will render, through the same * providers as main.tsx, so the two can never drift apart the way a * hand-written static twin would. What is different is only what render-time * knows: the locale comes from the URL or the Accept-Language header instead * of a navigator, the public config is handed in instead of fetched, and the * voice assignment was drawn by the caller — the server records the * impression, embeds the assignment beside this HTML, and the client adopts * it rather than drawing a second one. * * Built with `vite build --ssr` into its own bundle with every dependency * inlined, so the API server can import one file and needs no React of its * own. Effects never run in renderToString: nothing here fetches, beats, or * scrolls. */ export function renderMarketing(input: { locale: Locale; config: PublicConfig; voice: VoiceAssignment | null; }): string { const queryClient = new QueryClient(); queryClient.setQueryData(['publicConfig'], input.config); try { return renderToString( , ); } finally { queryClient.clear(); } } /** The head lines that go with it, from the same catalog as the page. */ export function marketingMeta(locale: Locale): { title: string; description: string } { return { title: messages['marketing.metaTitle'][locale], description: messages['marketing.metaDescription'][locale], }; }