Repository navigation
Commit 6f78ab6
perf(prerender): reuse each page's HTML render for its navigation data (#501)
## Problem
Static prerendering made two requests per page: one for the HTML, and a
second with `X-VoidPages` to write `_void/pages/*.json`. The loader and
middleware ran twice for every page.
## Fix
Fixes #500
The HTML already embeds the page object in `__VOID_PAGE_DATA__`, so the
prerenderer now writes the JSON from that script and skips the second
request.
- Uses `extractPageDataFromHtml` from `@void/isr/core`, which ISR
already uses to build JSON from page HTML.
- Pages with deferred props still make the second request. Their HTML
has those props missing, because the values are streamed after the page
data script. The prefetch request returns them filled in.
- In practice, defer() is mainly useful for server-rendered pages. A
fully static site rarely needs it, so on most static sites the two
implementations behave the same. IMO, this is an acceptable fix. The
alternative is to
- Island pages and non-page routes have no `__VOID_PAGE_DATA__` script,
so they still get no JSON file.
- The JSON is parsed and stringified again, so the files are
byte-identical to before. The embedded copy escapes `<`, `>` and `&`.
## Results
I ran two benchmarks, once with a 1000-page Void app (no actual network
requests), another with a remote PayloadCMS database.
### 1000-page Void app
Measured with my own 1000-page Void app benchmark, static output. Old
and new code ran alternately in one process on the same build:
| | Before | After |
|---|---|---|
| Requests | 2,002 | 1,001 |
| Prerender phase, Node | 422–477 ms | 380–396 ms (−10% to −19%) |
| Prerender phase, Cloudflare | 848–1,660 ms | 630–1,149 ms (−26% to
−31%) |
On Node, the second request was an in-process call, so a full build
barely changes: 4.99 s before vs 4.64 s after, median of 3, within
noise. The gain is larger on Cloudflare, where each request goes through
the local Workers runtime.
The built output (1,001 HTML and 1,001 JSON files) is byte-identical to
before on both Node and Cloudflare.
### 100-page blog backed by remote Payload CMS
Tested in a more realistic scenario of a 100-post blog backed by a
remote Payload CMS database, where each pre-rendered page fetches from
Payload CMS:
| | Before | After |
|---|---:|---:|
| Build time | 265.00s | 133.31s |
| Payload operations | 1,439 | 722 |
## Tests
- Updated two existing tests that served page data only through the
second request.
- Added one test for deferred pages, which still need the second
request, and for HTML without page data, which gets no JSON file.
- `static-pages` and `node-static` integration tests pass.
## Alternative considered
Another approach handles deferred props in the same single request.
During prerendering, the page handler copies each deferred value into
the page object as it streams, then appends the completed object to the
end of the HTML as a build-only script.
The prerenderer removes that script and writes it as the JSON file. This
avoids the second request even for deferred pages, but it changes
`handlePageGet`, which runs on every page request in every app, and the
appended script has to pass through the app's HTML middleware unchanged.
I prefer to keep the change in build-time code instead. Deferred props
are uncommon in fully static pages, so those pages keeping the second
request costs little.
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: LongYinan <github@lyn.one>1 parent f53f2b7 commit 6f78ab6
2 files changed
Lines changed: 75 additions & 15 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
2 | 3 | | |
3 | 4 | | |
| 5 | + | |
| 6 | + | |
4 | 7 | | |
5 | 8 | | |
6 | 9 | | |
| |||
157 | 160 | | |
158 | 161 | | |
159 | 162 | | |
160 | | - | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
161 | 171 | | |
162 | 172 | | |
163 | | - | |
164 | | - | |
165 | | - | |
| 173 | + | |
166 | 174 | | |
167 | 175 | | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
168 | 180 | | |
169 | 181 | | |
170 | 182 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
| 10 | + | |
| 11 | + | |
9 | 12 | | |
10 | 13 | | |
11 | 14 | | |
| |||
18 | 21 | | |
19 | 22 | | |
20 | 23 | | |
21 | | - | |
| 24 | + | |
22 | 25 | | |
23 | 26 | | |
| 27 | + | |
| 28 | + | |
24 | 29 | | |
25 | 30 | | |
26 | | - | |
| 31 | + | |
27 | 32 | | |
28 | 33 | | |
29 | 34 | | |
30 | 35 | | |
31 | 36 | | |
32 | 37 | | |
33 | 38 | | |
34 | | - | |
35 | | - | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
36 | 42 | | |
37 | 43 | | |
38 | | - | |
39 | | - | |
| 44 | + | |
40 | 45 | | |
41 | 46 | | |
42 | 47 | | |
43 | 48 | | |
44 | 49 | | |
45 | 50 | | |
46 | | - | |
47 | | - | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
48 | 57 | | |
49 | 58 | | |
50 | 59 | | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
51 | 94 | | |
52 | 95 | | |
53 | 96 | | |
| |||
122 | 165 | | |
123 | 166 | | |
124 | 167 | | |
| 168 | + | |
125 | 169 | | |
126 | 170 | | |
127 | 171 | | |
128 | 172 | | |
129 | 173 | | |
130 | 174 | | |
131 | | - | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
132 | 180 | | |
133 | | - | |
134 | 181 | | |
135 | 182 | | |
136 | 183 | | |
| |||
140 | 187 | | |
141 | 188 | | |
142 | 189 | | |
| 190 | + | |
143 | 191 | | |
144 | 192 | | |
145 | 193 | | |
146 | 194 | | |
147 | 195 | | |
148 | | - | |
| 196 | + | |
149 | 197 | | |
150 | 198 | | |
151 | 199 | | |
| |||
0 commit comments