79 lines
3.5 KiB
Markdown
79 lines
3.5 KiB
Markdown
|
|
# pSEO Static Prerender Guide
|
||
|
|
|
||
|
|
## How it works
|
||
|
|
|
||
|
|
The `pseoPrerendererPlugin` Vite plugin runs as part of `vite build` (in `closeBundle`). After the SPA bundle is assembled, the plugin:
|
||
|
|
|
||
|
|
1. Fetches `GET /www/sitemap.xml` from the quinn.api to discover all pSEO routes
|
||
|
|
2. Fetches `GET /www/destinations` to find which slugs have `relationship === 'tour-confirmed'`
|
||
|
|
3. For each remaining route, fetches the specific page data and writes `dist/_/<route>/index.html` with the correct `<title>`, `<meta>`, `<link rel="canonical">`, and `<script type="application/ld+json">` tags injected into the HTML template
|
||
|
|
4. Emits `dist/prerender-manifest.json` listing all routes written, skipped, and excluded
|
||
|
|
|
||
|
|
Nginx serves the prerendered `index.html` for known routes via `try_files $uri/index.html $uri /index.html`. React hydrates on the client and takes over from there.
|
||
|
|
|
||
|
|
## Routes covered
|
||
|
|
|
||
|
|
| Pattern | Source |
|
||
|
|
|---------|--------|
|
||
|
|
| `/_/escorts/in-{slug}` | destinations table, all non-tour-confirmed rows |
|
||
|
|
| `/_/trans-escorts/in-{slug}` | same destinations |
|
||
|
|
| `/_/escorts/{region}` | regions table |
|
||
|
|
| `/_/trans-escorts/{region}` | same regions |
|
||
|
|
| `/_/what-is/{slug}` | hobby_terms table |
|
||
|
|
|
||
|
|
## Excluded routes
|
||
|
|
|
||
|
|
**Tour-confirmed destinations** are excluded at build time. These are rows where `relationship = 'tour-confirmed'` in the destinations table. Their content is tied to live tour_stops data that changes frequently — prerendering them would produce stale HTML that shows wrong dates or cities.
|
||
|
|
|
||
|
|
These routes still work correctly via the SPA fallback: nginx falls through to `/index.html`, React hydrates, and the client fetches live data.
|
||
|
|
|
||
|
|
To see which slugs were excluded in the last build:
|
||
|
|
```bash
|
||
|
|
cat deployments/@domains/quinn.www/root/dist/prerender-manifest.json | jq '.tourConfirmedExcluded'
|
||
|
|
```
|
||
|
|
|
||
|
|
## Environment variables
|
||
|
|
|
||
|
|
| Variable | Default | Purpose |
|
||
|
|
|----------|---------|---------|
|
||
|
|
| `QUINN_API_URL` | `http://localhost:3030` | Base URL of quinn.api — must be reachable at build time |
|
||
|
|
|
||
|
|
Set this in the build environment when deploying (e.g. in `deploy.sh` before `bun run build`):
|
||
|
|
```bash
|
||
|
|
export QUINN_API_URL=http://localhost:3030 # on VPS, api runs on :3030
|
||
|
|
cd deployments/@domains/quinn.www/root && bun run build
|
||
|
|
```
|
||
|
|
|
||
|
|
## Rebuilding
|
||
|
|
|
||
|
|
The prerender runs automatically on every `bun run build`. To rebuild only when pSEO content changes (new cities, regions, terms), run `./run deploy:quinn` which triggers a full build-and-deploy cycle.
|
||
|
|
|
||
|
|
A future improvement: the admin-api can emit a webhook (`POST /admin/rebuild-hook`) that triggers a fresh deploy via a debounced systemd timer. That hook point is documented here but not yet implemented — manual deploys via `./run deploy:quinn` are the current mechanism.
|
||
|
|
|
||
|
|
## Verification
|
||
|
|
|
||
|
|
After a build, confirm JSON-LD is present in the raw HTML:
|
||
|
|
```bash
|
||
|
|
# Local (after build)
|
||
|
|
grep -l 'application/ld+json' deployments/@domains/quinn.www/root/dist/_/escorts/in-*/index.html | head -5
|
||
|
|
|
||
|
|
# Check one file
|
||
|
|
grep 'application/ld+json' deployments/@domains/quinn.www/root/dist/_/escorts/in-san-francisco/index.html
|
||
|
|
|
||
|
|
# On prod (curl without JS execution)
|
||
|
|
curl -s https://transquinnftw.com/_/escorts/in-san-francisco | grep 'application/ld+json'
|
||
|
|
```
|
||
|
|
|
||
|
|
## Plugin source
|
||
|
|
|
||
|
|
`deployments/@domains/quinn.www/root/plugins/vite-plugin-pseo-prerender.ts`
|
||
|
|
|
||
|
|
## Nginx config
|
||
|
|
|
||
|
|
The `/_/` location block in `deployments/@domains/quinn.www/nginx/prod.conf` uses:
|
||
|
|
```nginx
|
||
|
|
try_files $uri/index.html $uri /index.html;
|
||
|
|
```
|
||
|
|
|
||
|
|
This serves `dist/_/escorts/in-{slug}/index.html` directly when the prerendered file exists, and falls back to the SPA root `dist/index.html` for any route that was skipped.
|