prestruct docs

Complete API documentation for prestruct hooks and utilities.

usePageMeta

Keeps head tags in sync during client-side navigation.

Signature

usePageMeta({
  title,
  description,
  path,
  siteUrl
})

Parameters

Parameter Type Required Description
title string Yes Page title (used for <title>, og:title, twitter:title)
description string No Meta description (used for meta description, og:description, twitter:description)
path string Yes Route path for canonical URL generation
siteUrl string No Full site URL. Defaults to import.meta.env.VITE_SITE_URL

Usage

import usePageMeta from '../hooks/usePageMeta.js'

export default function About() {
  usePageMeta({
    siteUrl: 'https://example.com',
    path: '/about/',
    title: 'About | Example',
    description: 'Learn more about our mission.',
  })
  
  return <div>About page content</div>
}

Tags Updated

This hook updates these tags on navigation:

These tags remain unchanged (set once at load):

Tip: Wrapper Hook

Avoid repeating siteUrl in every page:

// src/hooks/useMeta.js
import usePageMeta from './usePageMeta.js'

const SITE_URL = import.meta.env.VITE_SITE_URL || 'https://example.com'

export default function useMeta(config) {
  return usePageMeta({
    siteUrl: SITE_URL,
    ...config
  })
}

// Now in pages:
import useMeta from '../hooks/useMeta.js'

function About() {
  useMeta({
    path: '/about/',
    title: 'About | Example',
    description: '...',
  })
}

mountIslands

Mounts dynamic React components into <pre-island> placeholders.

Signature

mountIslands(registry)

Parameters

Parameter Type Required Description
registry object Yes Map of island names to React components

Usage

import { mountIslands } from './islands.js'
import { islands } from './AppIslands.jsx'

// In AppLayout.jsx - call after each navigation
useEffect(() => {
  const timer = setTimeout(() => mountIslands(islands), 0)
  return () => clearTimeout(timer)
}, [pathname])

Registry Format

// src/AppIslands.jsx
import RecentlyViewed from './islands/RecentlyViewed.jsx'
import CartWidget from './islands/CartWidget.jsx'

export const islands = {
  'recently-viewed': RecentlyViewed,
  'cart-widget': CartWidget,
}

pre-island Element

Custom element for declaring dynamic content.

Attributes

Attribute Values Default Description
data-pre-island string Required Island name (must match registry key)
data-pre-load eager | visible | idle eager Load strategy

Examples

// Mounts immediately after hydration
<pre-island data-pre-island="recently-viewed" />

// Mounts when scrolled into view
<pre-island data-pre-island="cart-widget" data-pre-load="visible">
  <span className="loading">Loading cart...</span>
</pre-island>

// Mounts during browser idle time
<pre-island data-pre-island="promo-banner" data-pre-load="idle" />

Fallback Content

Content inside <pre-island> renders in the static HTML and is visible to crawlers:

<pre-island data-pre-island="cart-widget">
  <div className="cart-skeleton">
    <div className="skeleton-line"></div>
    <div className="skeleton-line"></div>
  </div>
</pre-island>

This shows a skeleton while the React component loads.


Load Strategies

eager (default)

Mounts immediately after mountIslands() is called.

<pre-island data-pre-island="header-cart" />

visible

Mounts when the element enters the viewport using IntersectionObserver.

<pre-island data-pre-island="footer-newsletter" data-pre-load="visible" />

idle

Mounts during browser idle time using requestIdleCallback. Falls back to setTimeout(200) if unavailable.

<pre-island data-pre-island="chat-widget" data-pre-load="idle" />

Island Components

Props

mountIslands() renders each registry component with no props – ReactDOM.createRoot(el).render(React.createElement(Component)) – and they cannot access React context from the main tree. To pass data:

Option 1: Use global state / localStorage

export default function CartWidget() {
  const [items, setItems] = useState([])
  
  useEffect(() => {
    // Read from your state management or localStorage
    const cart = JSON.parse(localStorage.getItem('cart') || '[]')
    setItems(cart)
  }, [])
  
  return <div>{items.length} items in cart</div>
}

ssr.config.js

Required Fields

export default {
  siteUrl: 'https://example.com',  // No trailing slash
  appLayoutPath: '/src/AppLayout.jsx',  // Optional -- this exact path is the engine default
  routes: [
    {
      path: '/',
      meta: { title: 'Home', description: '...' }
    }
  ]
}

Optional Fields

export default {
  // Site identity
  siteName: 'Example',
  author: 'Your Name',
  tagline: 'Your tagline',
  ogImage: '/og-default.png',
  keywords: 'keyword1, keyword2',
  
  // Routes
  routes: [
    {
      path: '/',
      priority: '1.0',         // Sitemap priority (number or string)
      changefreq: 'weekly',   // daily | weekly | monthly | yearly
      meta: {
        title: 'Home | Example',
        description: '...',
        ogImage: '/custom-og.png',  // Optional per-route OG image override
      }
    }
  ],
  
  // JSON-LD structured data
  buildJsonLd() {
    return [
      {
        '@context': 'https://schema.org',
        '@type': 'Organization',
        name: 'Example',
        url: 'https://example.com'
      }
    ]
  },
  
  // Bot proxy (optional) -- cache-refresh auth is the PRESTRUCT_SECRET env
  // var, never config. Unset secret = refresh disabled, not allow-all.
  proxy: {
    url: 'https://proxy.example.com',
    targetUrl: 'https://origin.example.com',   // optional
    botList: ['googlebot', 'bingbot']
  },

  // Opt-in robots.txt generation (false = public/robots.txt stays untouched)
  robots: true,

  // Custom 404 content (prerendered with noindex, always)
  notFound: {
    heading: 'Page not found',
    body: "That page doesn't exist.",
    primaryCta: { label: 'Go home', href: '/' },
  }
}

See the full config reference for every key, including platform, deployId, deployIdFooter, and fetchRoutes().


Environment Variables

Build Time

Variable Description
VITE_SITE_URL Base URL used by usePageMeta when no explicit siteUrl is passed
PRESTRUCT_STRICT=1 Fail the build (exit 1) on config diagnostics; off by default, diagnostics print and the build continues with the valid routes

Proxy Runtime (set where the proxy runs, not in the Pages app)

Variable Description
PRESTRUCT_SECRET Shared secret for the x-prestruct-refresh cache bust
PRESTRUCT_TARGET_URL Worker-only: origin the proxy renders
PRESTRUCT_CACHE_DIR VPS-only: relocate the render cache directory (default .prestruct_cache)

Next steps