Prerender
Client-rendered SPAs are invisible to most crawlers — Googlebot has been less of a problem in recent years, but Facebook, WhatsApp, Slack, Twitter, and most non-Google indexers still expect server-rendered HTML. @mongez/vite can emit a prerender.php and add a rewrite rule to the .htaccess so bot user-agents get a pre-rendered version while humans hit your SPA directly.
Default behaviour
mongezVite(); // preRender: false (opt-in)No prerender.php is emitted. No crawler routing is added to the .htaccess.
Opting in
mongezVite({ htaccess: true, // required — the rewrite rule lives here preRender: { url: "https://render.mentoor.io", crawlers: "Google-Site-Verification|Googlebot|facebook|crawl|WhatsApp|bot|Slack|Twitter", delay: 5000, cache: false, },});Options
| Key | Type | Default | What it does |
|---|---|---|---|
url | string | — (required) | The external prerender service to fetch from. Receives the visited URL as a query param and returns rendered HTML. |
crawlers | string | Google-Site-Verification|Googlebot|facebook|crawl|WhatsApp|bot|Slack|Twitter|bot | Pipe-separated regex alternation of user-agent fragments. Matched case-insensitively against User-Agent header. |
delay | number | 5000 | Milliseconds to tell the prerender service to wait before rendering — lets your SPA finish loading data before HTML is captured. |
cache | boolean | false | When truthy, the generated PHP caches each rendered URL under <outDir>/cache/<sha1(url)>.html. |
The generated prerender.php
<?php
function get_content($URL) { $hashedUrl = sha1($URL); $cachedPagesDirectory = __DIR__ . '/cache'; $cachedFile = $cachedPagesDirectory . '/' . $hashedUrl . '.html';
if (file_exists($cachedFile)) { $file = file_get_contents($cachedFile); if ($file) return $file; }
$ch = curl_init(); curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_URL, $URL); $data = curl_exec($ch); curl_close($ch);
if (!file_exists($cachedPagesDirectory)) { mkdir($cachedPagesDirectory, 0777, true); } file_put_contents($cachedFile, $data); return $data;}
$prerenderUrl = '<your prerender URL>';$url = $_SERVER['SCRIPT_URI'] ?? ...;$userAgent = $_SERVER['HTTP_USER_AGENT'];
$params = [ 'url' => $url, 'delay' => <delay>, 'cache' => <cache>, '__agent' => $userAgent,];
$url = "$prerenderUrl?" . http_build_query($params);$content = get_content($url);echo $content;It always writes to the cache directory regardless of the cache option — cache: true just enables the read path. If you don’t want any caching, delete the cache directory after each deploy or wrap the script.
The htaccess rewrite
When preRender is set, the .htaccess includes:
# PrerenderRewriteCond %{HTTP_USER_AGENT} .*(<crawlers>).* [NC]RewriteCond %{REQUEST_URI} !^(/public)RewriteRule (.*) prerender.php [L,QSA]So any request whose User-Agent matches the crawler list (and doesn’t start with /public) is routed to prerender.php instead of falling through to the SPA’s index.html.
Worked example
mongezVite({ htaccess: true, preRender: { url: "https://render.mentoor.io", cache: true, },});After vite build:
dist/├── .htaccess ← contains the prerender rewrite├── prerender.php ← proxies to https://render.mentoor.io├── cache/ ← created lazily on first crawler hit└── ...A request from Googlebot:
- Hits
dist/. - The
.htaccessmatchesUser-Agent: Googlebot...and rewrites toprerender.php. prerender.phpcheckscache/<sha1>.html. On cache miss it fetcheshttps://render.mentoor.io?url=...&delay=5000&cache=1&__agent=Googlebot/....- The response is cached and returned.
A request from a real user agent (Mozilla/...):
- Hits
dist/. - The
.htaccessdoes not match the crawler list and falls through to the SPA rewrite (index.html). - The SPA renders normally.
Sharp edges
preRender.urlis required whenpreRenderis enabled.defaultOptions.tshaspreRender: false, and there is no implicit default forurl. If you setpreRender: {}withouturl,generateHtaccess()throws a descriptive build-time error (preRender.url is required when preRender is enabled). Always seturlexplicitly.- Requires
htaccess: true. The wholegenerateHtaccessstep short-circuits whenhtaccess: false, so neither.htaccessnorprerender.phpis emitted. PairpreRenderwithhtaccess: trueexplicitly. - No Apache means no prerender. This pipeline is Apache-specific (
.htaccess+ PHP). Nginx / Caddy hosts need a different strategy. - The cache directory is created lazily by the PHP. Make sure the web server has write permission to
<outDir>/cache/. __agentis forwarded to the service so its rendering can detect the original crawler. Whether your service uses it depends on its implementation.