diff --git a/index.html b/index.html
index 9fae366..411f22f 100644
--- a/index.html
+++ b/index.html
@@ -2,8 +2,8 @@
-
-
+
+
@@ -20,23 +20,23 @@
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
@@ -48,10 +48,10 @@
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600&family=Playfair+Display:ital,wght@0,900;1,900&family=JetBrains+Mono&display=swap"
/>
- OneHook — Intentional Messaging App for One Real Connection
+ OneHook — Intentional Dating App for One Real Connection
-
+
\n `);
+ async closeBundle() {
+ const indexPath = resolve(distDir, 'index.html');
+ if (!existsSync(indexPath)) return; // the build failed; let its own error surface
+ const base = readFileSync(indexPath, 'utf8');
+
+ // Build the page renderer as an SSR bundle (Rollup resolves CommonJS dependencies the same way
+ // the browser build does), then import it in Node. Written to a temp dir and removed afterwards,
+ // so nothing from it can reach dist/.
+ const ssrOutDir = resolve(__dirname, 'node_modules/.cache/prerender');
+ await build({
+ configFile: false,
+ root: __dirname,
+ mode,
+ logLevel: 'error',
+ plugins: [react()],
+ resolve: { alias: appAliases },
+ define: { 'process.env.NODE_ENV': JSON.stringify(mode) },
+ ssr: { noExternal: true },
+ build: {
+ ssr: resolve(__dirname, 'src/prerender-entry.tsx'),
+ outDir: ssrOutDir,
+ emptyOutDir: true,
+ copyPublicDir: false,
+ rollupOptions: { output: { entryFileNames: 'prerender-entry.mjs', format: 'es', inlineDynamicImports: true } },
+ },
+ });
+ try {
+ const { renderRouteBody, PRERENDER_BODY_PAGES } = (await import(
+ pathToFileURL(resolve(ssrOutDir, 'prerender-entry.mjs')).href
+ )) as typeof import('./src/prerender-entry');
+
+ const setAttr = (html: string, selector: RegExp, value: string) => {
+ if (!selector.test(html)) throw new Error(`[prerender-pages] tag not found in index.html: ${selector}`);
+ return html.replace(selector, (_m, pre: string, post: string) => `${pre}${escapeHtml(value)}${post}`);
+ };
+ const banner = smartAppBannerContent();
+
+ const renderPage = (route: string, seo: RouteSeo, canonical: string | null): string => {
+ const image = `${SITE_URL}${seo.image ?? DEFAULT_SHARE_IMAGE}`;
+ const imageAlt = seo.imageAlt ?? DEFAULT_SHARE_IMAGE_ALT;
+ let html = base;
+ html = html.replace(/[^<]*<\/title>/, `${escapeHtml(seo.title)}`);
+ html = setAttr(html, /(/, '').replace(/\s*/, '');
+ }
+ html = setAttr(html, /(`);
+ if (seo.jsonLd) {
+ const ld = JSON.stringify(seo.jsonLd).replace(/${ld}`);
+ }
+ if (extraHead.length) html = html.replace('', ` ${extraHead.join('\n ')}\n `);
+ if (PRERENDER_BODY_PAGES[route]) {
+ if (!html.includes('')) throw new Error('[prerender-pages] not found in index.html');
+ const body = revealMotionInitialStyles(renderRouteBody(route));
+ html = html.replace('', () => `${body}
`);
+ }
+ return html;
+ };
+
+ for (const route of ['/', ...PRERENDER_ROUTES]) {
+ const html = renderPage(route, ROUTE_SEO[route], `${SITE_URL}${route}`);
+ const dir = route === '/' ? distDir : resolve(distDir, route.replace(/^\//, ''));
+ mkdirSync(dir, { recursive: true });
+ writeFileSync(resolve(dir, 'index.html'), html);
}
- const dir = resolve(distDir, route.replace(/^\//, ''));
- mkdirSync(dir, { recursive: true });
- writeFileSync(resolve(dir, 'index.html'), html);
+ writeFileSync(resolve(distDir, '404.html'), renderPage('/404', NOT_FOUND_SEO, null));
+ writeFileSync(resolve(distDir, 'sitemap.xml'), buildSitemap());
+ } finally {
+ await rm(ssrOutDir, { recursive: true, force: true });
}
},
});
+const appAliases = [
+ { find: '@', replacement: resolve(__dirname, 'src') },
+ { find: /onehook-api-client\/dist-es\/runtimeConfig$/, replacement: 'onehook-api-client/dist-es/runtimeConfig.browser' },
+];
+
const leanMediaPrunePlugin = (): Plugin => ({
name: 'lean-media-prune',
apply: 'build',
@@ -140,37 +240,9 @@ export default defineConfig(({ mode }) => {
tailwindcss(),
ensureOutDirPlugin(),
leanMediaPrunePlugin(),
- prerenderRouteHeadsPlugin(),
- Sitemap({
- hostname: 'https://onehook.club',
- // Public marketing pages are discovered from the pre-built dist//index.html files written
- // by prerenderRouteHeadsPlugin (which runs first), so they are not listed again here — listing
- // them would duplicate every entry. '/' comes from dist/index.html.
- dynamicRoutes: [],
- exclude: ['/app', '/onboarding', '/auth/setup', '/login', '/redeem'],
- // Generated robots.txt must mirror public/robots.txt (the plugin overwrites dist/robots.txt).
- robots: [
- {
- userAgent: '*',
- allow: '/',
- disallow: ['/app', '/onboarding', '/auth/', '/redeem'],
- },
- ],
- // compact output: no whitespace inside , which some validators reject
- readable: false,
- changefreq: 'weekly',
- priority: {
- '/': 1.0,
- '/science': 0.8,
- '/messenger': 0.8,
- '/pricing': 0.8,
- '/careers': 0.6,
- '/contact': 0.6,
- '/privacy': 0.4,
- '/terms': 0.4,
- },
- outDir: 'dist',
- }),
+ // Writes per-route HTML (head + rendered body), 404.html and sitemap.xml. robots.txt is
+ // public/robots.txt, copied as-is.
+ prerenderPagesPlugin(mode),
],
server: {
fs: {
@@ -263,10 +335,7 @@ export default defineConfig(({ mode }) => {
},
},
resolve: {
- alias: [
- { find: '@', replacement: resolve(__dirname, 'src') },
- { find: /onehook-api-client\/dist-es\/runtimeConfig$/, replacement: 'onehook-api-client/dist-es/runtimeConfig.browser' },
- ],
+ alias: appAliases,
},
define: {
'process.env.NODE_ENV': JSON.stringify(mode),