Prestruct handles technical SEO at build time. This guide covers best practices.
How Prestruct Works
Prestruct prerenders each route to static HTML with:
<title>tags<meta name="description">- Open Graph tags (
og:title,og:description,og:image,og:url) - Twitter Card meta tags
- Canonical URLs
- JSON-LD structured data
Configuration
Define routes in ssr.config.js:
export default {
siteUrl: 'https://example.com',
siteName: 'Example',
ogImage: 'https://example.com/og-image.png',
routes: [
{
path: '/',
priority: '1.0',
changefreq: 'weekly',
meta: {
title: 'Home | Example',
description: 'Your homepage description (150-160 chars)',
}
},
{
path: '/about/',
priority: '0.8',
changefreq: 'monthly',
meta: {
title: 'About Us | Example',
description: 'Learn more about our company...',
}
}
]
}
Meta Best Practices
Title Tags
- Keep under 60 characters
- Include primary keyword near the start
- Unique for every page
Descriptions
- 150-160 characters optimal
- Include primary and secondary keywords
- Unique for every page
Canonical URLs
Canonical is siteUrl + the route path, minus the trailing slash on /:
// Route path written with a trailing slash
{ path: '/about/' }
// Generates: <link rel="canonical" href="https://example.com/about/" />
// Root is the bare site origin (no trailing slash)
{ path: '/' }
// Generates: <link rel="canonical" href="https://example.com" />
usePageMeta computes the same value client-side on SPA navigation, so the
canonical never flips between SSR and client.
Structured Data (JSON-LD)
Add schema.org data in ssr.config.js:
export default {
// ...other config
buildJsonLd() {
return [
{
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'Example Inc',
url: 'https://example.com',
logo: 'https://example.com/logo.png',
contactPoint: {
'@type': 'ContactPoint',
telephone: '+1-555-1234',
contactType: 'customer service'
}
}
]
}
}
The returned object(s) are injected as <script type="application/ld+json">
into every page. buildJsonLd` is called with no arguments and cannot vary by
route – see Structured Data for the full set of
types and the per-route limitation.
Sitemap
Prestruct auto-generates sitemap.xml with:
- All routes from config (merged with
fetchRoutes()output; static wins) <lastmod>with the build date (YYYY-MM-DD)<changefreq>from config, defaulting tomonthlywhen omitted<priority>from config, defaulting to0.5when omitted- XML-escaped loc values; the home URL carries no trailing slash
404 Pages
Prestruct generates a 404 page with noindex robots directive:
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet, noimageindex" />
This prevents 404 pages from appearing in search results.
Robots.txt
Two ways, one contract: prestruct never touches dist/robots.txt unless you
opt in. Author your own in public/robots.txt (copied through untouched):
User-agent: *
Allow: /
Sitemap: https://example.com/sitemap.xml
Or generate one from config (robots: true = minimal allow-all + Sitemap
line, robots: "<body>" = custom body with the Sitemap line auto-appended,
robots: { body, sitemap: false } = custom body without the pointer). Both
come from the robots key in ssr.config.js; details:
Config Reference.
AI crawlers (AEO)
robots: { aeo: true } writes a per-bot policy instead of a catch-all:
search-visibility crawlers are allowed, training crawlers are disallowed by
default (aeo: { aiBots: 'allow' } flips that). The separation matters
because the vendor tokens differ: OAI-SearchBot controls ChatGPT search
results while GPTBot controls OpenAI training crawls; Claude-SearchBot vs
ClaudeBot; Applebot vs Applebot-Extended. Google-Extended is a product
token for Gemini/Vertex grounding that does not affect Google Search
inclusion. Details: Config Reference.
Two honest limits, from vendor documentation (2026):
- These are crawler instructions, not citation switches. No tag or file makes
an answer engine cite you; Google’s 2026 guidance is explicit that ordinary
fundamentals remain what matter and it ignores
llms.txt. - robots rules are not access control. A disallowed URL may still be indexed
by some systems without a description, and
Perplexity-User/ChatGPT-User-style user-initiated fetches may ignore robots entirely.
What does move answer-engine visibility, per the same documentation: public crawlable HTML (prestruct prerenders every route, so this is already handled), stable canonicals, accurate titles/descriptions, visible author/date info, schema.org types that match visible content (Article, Person/ProfilePage, BreadcrumbList – see Structured Data), and not accidentally blocking resource URLs. FAQ rich results were retired May 2026; de-prioritize FAQPage markup unless the page is genuinely Q&A.
Security Headers
Prestruct sites on Cloudflare Pages get security headers automatically:
Strict-Transport-SecurityContent-Security-PolicyX-Content-Type-OptionsX-Frame-Options
Validation
Use these tools to verify SEO:
Gotchas & Troubleshooting
Duplicate Meta Tags
If you see duplicate <title> or <meta name="robots"> tags, check your public/index.html for pre-existing meta tags. Prestruct’s inject-brand.js adds meta tags dynamically - remove duplicate meta from your base HTML.
404 Pages Not Indexed
Prestruct automatically adds noindex directives to 404 pages to prevent them from appearing in search results:
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet, noimageindex" />
This is correct behavior - don’t remove it unless you want 404s indexed.
Trailing Slash Mismatch
If your canonical URL has a trailing slash but users access without (or vice versa), you may see duplicate content warnings. Choose one style in your route definitions and stay consistent.
Next steps
- Structured Data - JSON-LD patterns and validation
- Redirects -
_redirects/_headerson Cloudflare Pages - Routing - route paths, trailing slashes, 404s