Centralize public route authority

This commit is contained in:
axiomlogicnexus 2026-06-24 00:24:32 +00:00
parent 8d6334a34d
commit d498131dd8
10 changed files with 260 additions and 38 deletions

View file

@ -144,6 +144,7 @@ run_step \
src/__tests__/protected-app-pages.test.tsx \
src/__tests__/DashboardOverviewPage.test.tsx \
src/__tests__/app-route-tree.test.tsx \
src/__tests__/public-route-registry.test.ts \
src/__tests__/route-shells.test.tsx \
src/__tests__/release-manifest.test.ts \
src/__tests__/public-marketing-pages.test.tsx \

View file

@ -14,6 +14,7 @@ First-party `hypertwist.app` surface for HyperTwist:
- public launch-status callouts across pricing, download, and notices surfaces
- route-aware metadata, canonical, Open Graph, Twitter-card, and protected-route noindex posture for the live `hypertwist.app` surface
- first-party `robots.txt` plus `sitemap.xml` for the public crawlable route set while keeping `/app`, `/login`, and `/register` out of crawler posture
- a shared public-route registry that now drives marketing navigation, footer links, router entries, and sitemap generation from one source of truth
- runtime diagnostics that distinguish local, mixed, and public auth deployment posture
- public open-source notices surface required by HyperTwist's MPL distribution doctrine

View file

@ -6,10 +6,11 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build": "npm run render:sitemap && tsc && vite build",
"preview": "vite preview",
"type-check": "tsc --noEmit",
"test": "vitest run",
"render:sitemap": "node scripts/render-public-sitemap.mjs",
"check:runtime-readiness": "node scripts/check-runtime-readiness.mjs",
"render:same-origin-deployment": "node scripts/render-same-origin-deployment.mjs",
"render:same-origin-bundle": "node scripts/render-same-origin-bundle.mjs",

View file

@ -0,0 +1,24 @@
import { readFile, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))
const registryPath = path.join(__dirname, '..', 'src', 'public-route-registry.json')
const sitemapPath = path.join(__dirname, '..', 'public', 'sitemap.xml')
const baseUrl = 'https://hypertwist.app'
const routeRegistry = JSON.parse(await readFile(registryPath, 'utf8'))
const crawlableRoutes = routeRegistry.filter((route) => route && route.crawlable === true)
const xml = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
...crawlableRoutes.map((route) => (
` <url>\n <loc>${baseUrl}${route.path === '/' ? '/' : route.path}</loc>\n </url>`
)),
'</urlset>',
'',
].join('\n')
await writeFile(sitemapPath, xml, 'utf8')
console.log(`Rendered sitemap for ${crawlableRoutes.length} crawlable routes to ${sitemapPath}`)

View file

@ -0,0 +1,46 @@
import { describe, expect, it } from 'vitest'
import {
buildPublicSitemapXml,
crawlablePublicRoutes,
footerLinks,
marketingNavLinks,
publicRouteRegistry,
} from '../public-route-registry'
describe('public route registry', () => {
it('keeps unique route definitions for the public shell', () => {
const uniquePaths = new Set(publicRouteRegistry.map((route) => route.path))
expect(uniquePaths.size).toBe(publicRouteRegistry.length)
})
it('exposes the intended primary navigation and footer surfaces', () => {
expect(marketingNavLinks).toEqual([
{ to: '/', label: 'Home' },
{ to: '/features', label: 'Features' },
{ to: '/about', label: 'About' },
{ to: '/resources', label: 'Resources' },
{ to: '/pricing', label: 'Pricing' },
{ to: '/download', label: 'Download' },
])
expect(footerLinks).toContainEqual({
to: '/open-source-notices',
label: 'Open Source Notices',
})
})
it('keeps auth routes out of crawlable sitemap posture', () => {
expect(crawlablePublicRoutes.map((route) => route.path)).not.toContain('/login')
expect(crawlablePublicRoutes.map((route) => route.path)).not.toContain('/register')
})
it('renders sitemap XML from the shared crawlable route registry', () => {
const xml = buildPublicSitemapXml()
expect(xml).toContain('<loc>https://hypertwist.app/</loc>')
expect(xml).toContain('<loc>https://hypertwist.app/features</loc>')
expect(xml).not.toContain('/login')
expect(xml).not.toContain('/register')
})
})

View file

@ -2,7 +2,7 @@ import { Link, NavLink } from 'react-router-dom'
import { usePlatformAuth } from '../../auth/platform-auth'
import { MarketingLaunchStatusBanner } from '../ui/PublicLaunchStatus'
import { brandConfig } from '../../site-config'
import { footerLinks, marketingNavLinks } from '../../site-data'
import { footerLinks, marketingNavLinks } from '../../public-route-registry'
export function MarketingShell({
title,

View file

@ -0,0 +1,122 @@
[
{
"path": "/",
"label": "Home",
"loaderTitle": "Loading HyperTwist...",
"nav": true,
"footer": false,
"crawlable": true
},
{
"path": "/features",
"label": "Features",
"loaderTitle": "Feature atlas",
"nav": true,
"footer": true,
"crawlable": true
},
{
"path": "/about",
"label": "About",
"loaderTitle": "About HyperTwist",
"nav": true,
"footer": true,
"crawlable": true
},
{
"path": "/resources",
"label": "Resources",
"loaderTitle": "Resources",
"nav": true,
"footer": true,
"crawlable": true
},
{
"path": "/docs",
"label": "Docs",
"loaderTitle": "Documentation",
"nav": false,
"footer": false,
"crawlable": true
},
{
"path": "/support",
"label": "Support",
"loaderTitle": "Support",
"nav": false,
"footer": false,
"crawlable": true
},
{
"path": "/changelog",
"label": "Release notes",
"loaderTitle": "Release notes",
"nav": false,
"footer": false,
"crawlable": true
},
{
"path": "/pricing",
"label": "Pricing",
"loaderTitle": "Pricing",
"nav": true,
"footer": true,
"crawlable": true
},
{
"path": "/download",
"label": "Download",
"loaderTitle": "Download center",
"nav": true,
"footer": true,
"crawlable": true
},
{
"path": "/open-source-notices",
"label": "Open Source Notices",
"loaderTitle": "Open source notices",
"nav": false,
"footer": true,
"crawlable": true
},
{
"path": "/privacy",
"label": "Privacy",
"loaderTitle": "Privacy",
"nav": false,
"footer": true,
"crawlable": true
},
{
"path": "/terms",
"label": "Terms",
"loaderTitle": "Terms",
"nav": false,
"footer": true,
"crawlable": true
},
{
"path": "/shipping-payment",
"label": "Shipping & Payment",
"loaderTitle": "Shipping & payment",
"nav": false,
"footer": true,
"crawlable": true
},
{
"path": "/login",
"label": "Log in",
"loaderTitle": "Log in",
"nav": false,
"footer": false,
"crawlable": false
},
{
"path": "/register",
"label": "Create account",
"loaderTitle": "Create account",
"nav": false,
"footer": false,
"crawlable": false
}
]

View file

@ -0,0 +1,37 @@
import routeRegistry from './public-route-registry.json'
export type PublicRouteDefinition = {
path: string
label: string
loaderTitle: string
nav: boolean
footer: boolean
crawlable: boolean
}
export const publicRouteRegistry = routeRegistry as PublicRouteDefinition[]
export const marketingNavLinks = publicRouteRegistry
.filter((route) => route.nav)
.map((route) => ({
to: route.path,
label: route.label,
}))
export const footerLinks = publicRouteRegistry
.filter((route) => route.footer)
.map((route) => ({
to: route.path,
label: route.label,
}))
export const crawlablePublicRoutes = publicRouteRegistry.filter((route) => route.crawlable)
export function buildPublicSitemapXml(baseUrl = 'https://hypertwist.app') {
const normalizedBaseUrl = baseUrl.replace(/\/$/, '')
const urlEntries = crawlablePublicRoutes
.map((route) => ` <url>\n <loc>${normalizedBaseUrl}${route.path === '/' ? '/' : route.path}</loc>\n </url>`)
.join('\n')
return `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${urlEntries}\n</urlset>\n`
}

View file

@ -1,6 +1,7 @@
import { Suspense, lazy, type ReactNode } from 'react'
import { Route } from 'react-router-dom'
import { GeneralPageLoader } from '../components/ui/Skeletons'
import { publicRouteRegistry } from '../public-route-registry'
const HomeLanding = lazy(() => import('../pages/public-pages-marketing').then((m) => ({ default: m.HomeLanding })))
const FeaturesPage = lazy(() => import('../pages/public-pages-features').then((m) => ({ default: m.FeaturesPage })))
@ -18,6 +19,24 @@ const ShippingPaymentPage = lazy(() => import('../pages/public-pages-commerce').
const LoginPage = lazy(() => import('../pages/auth-pages').then((m) => ({ default: m.LoginPage })))
const RegisterPage = lazy(() => import('../pages/auth-pages').then((m) => ({ default: m.RegisterPage })))
const publicRouteElements: Record<string, ReactNode> = {
'/': <HomeLanding />,
'/features': <FeaturesPage />,
'/about': <AboutPage />,
'/resources': <ResourcesPage />,
'/docs': <DocsPage />,
'/support': <SupportPage />,
'/changelog': <ChangelogPage />,
'/pricing': <PricingPage />,
'/download': <DownloadPage />,
'/open-source-notices': <OpenSourceNoticesPage />,
'/privacy': <PrivacyPage />,
'/terms': <TermsPage />,
'/shipping-payment': <ShippingPaymentPage />,
'/login': <LoginPage />,
'/register': <RegisterPage />,
}
function withGeneralLoader(title: string, element: ReactNode) {
return <Suspense fallback={<GeneralPageLoader title={title} />}>{element}</Suspense>
}
@ -25,21 +44,13 @@ function withGeneralLoader(title: string, element: ReactNode) {
export function renderPublicRoutes() {
return (
<>
<Route path="/" element={withGeneralLoader('Loading HyperTwist...', <HomeLanding />)} />
<Route path="/features" element={withGeneralLoader('Feature atlas', <FeaturesPage />)} />
<Route path="/about" element={withGeneralLoader('About HyperTwist', <AboutPage />)} />
<Route path="/resources" element={withGeneralLoader('Resources', <ResourcesPage />)} />
<Route path="/docs" element={withGeneralLoader('Documentation', <DocsPage />)} />
<Route path="/support" element={withGeneralLoader('Support', <SupportPage />)} />
<Route path="/changelog" element={withGeneralLoader('Release notes', <ChangelogPage />)} />
<Route path="/pricing" element={withGeneralLoader('Pricing', <PricingPage />)} />
<Route path="/download" element={withGeneralLoader('Download center', <DownloadPage />)} />
<Route path="/open-source-notices" element={withGeneralLoader('Open source notices', <OpenSourceNoticesPage />)} />
<Route path="/privacy" element={withGeneralLoader('Privacy', <PrivacyPage />)} />
<Route path="/terms" element={withGeneralLoader('Terms', <TermsPage />)} />
<Route path="/shipping-payment" element={withGeneralLoader('Shipping & payment', <ShippingPaymentPage />)} />
<Route path="/login" element={withGeneralLoader('Log in', <LoginPage />)} />
<Route path="/register" element={withGeneralLoader('Create account', <RegisterPage />)} />
{publicRouteRegistry.map((route) => (
<Route
key={route.path}
path={route.path}
element={withGeneralLoader(route.loaderTitle, publicRouteElements[route.path])}
/>
))}
</>
)
}

View file

@ -26,15 +26,6 @@ export const capabilityPillars = [
},
] as const
export const marketingNavLinks = [
{ to: '/', label: 'Home' },
{ to: '/features', label: 'Features' },
{ to: '/about', label: 'About' },
{ to: '/resources', label: 'Resources' },
{ to: '/pricing', label: 'Pricing' },
{ to: '/download', label: 'Download' },
] as const
export const deliverySurfaceCards = [
{
title: 'Public website',
@ -648,18 +639,6 @@ export const sourceAvailability = {
releaseNotesUrl,
}
export const footerLinks = [
{ to: '/features', label: 'Features' },
{ to: '/about', label: 'About' },
{ to: '/resources', label: 'Resources' },
{ to: '/pricing', label: 'Pricing' },
{ to: '/download', label: 'Download' },
{ to: '/open-source-notices', label: 'Open Source Notices' },
{ to: '/privacy', label: 'Privacy' },
{ to: '/terms', label: 'Terms' },
{ to: '/shipping-payment', label: 'Shipping & Payment' },
] as const
export const supportFaqs = [
{
question: 'Is the simulator fully in the browser?',