Skip to content

Commit cc4a793

Browse files
lilnasybluwyElianCodes
authored
Add Incremental Static Regeneration support for the Netlify's on-demand builders adapter (#7975)
* feat(netlify): expose builders ttl as a local * add changeset * docs(netlify): caching using on-demand builders * reword readme section * Update packages/integrations/netlify/package.json Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com> * include builders-types.d.ts in the distribution * document caveat regarding query params * update changeset * mutation -> function * locals.netlify -> locals.runtime * update types and changeset * Apply suggestions from code review Co-authored-by: Elian ☕️ <hello@elian.codes> * Apply suggestions from code review Co-authored-by: Elian ☕️ <hello@elian.codes> --------- Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com> Co-authored-by: Elian ☕️ <hello@elian.codes>
1 parent 59f205a commit cc4a793

6 files changed

Lines changed: 96 additions & 3 deletions

File tree

packages/integrations/netlify/README.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,30 @@ Once you run `astro build` there will be a `dist/_redirects` file. Netlify will
162162
> **Note**
163163
> You can still include a `public/_redirects` file for manual redirects. Any redirects you specify in the redirects config are appended to the end of your own.
164164
165+
### On-demand Builders
166+
167+
[Netlify On-demand Builders](https://docs.netlify.com/configure-builds/on-demand-builders/) are serverless functions used to generate web content as needed that’s automatically cached on Netlify’s Edge CDN. You can enable their use, using the [`builders` configuration](#builders).
168+
169+
By default, all pages will be rendered on first visit and the rendered result will be reused for every subsequent visit until you redeploy. To set a revalidation time, call the [`runtime.setBuildersTtl(ttl)` local](https://docs.astro.build/en/guides/middleware/#locals) with the duration (in seconds).
170+
171+
As an example, for the following snippet, Netlify will store the rendered HTML for 45 seconds.
172+
173+
```astro
174+
---
175+
import Layout from '../components/Layout.astro';
176+
177+
if (import.meta.env.PROD) {
178+
Astro.locals.runtime.setBuildersTtl(45);
179+
}
180+
---
181+
182+
<Layout title="Astro on Netlify">
183+
{new Date(Date.now())}
184+
</Layout>
185+
```
186+
187+
It is important to note that On-demand Builders ignore query params when checking for cached pages. For example, if `example.com/?x=y` is cached, it will be served for `example.com/?a=b` (different query params) and `example.com/` (no query params) as well.
188+
165189
## Usage
166190

167191
[Read the full deployment guide here.](https://docs.astro.build/en/guides/deploy/netlify/)
@@ -206,7 +230,7 @@ directory = "dist/functions"
206230
207231
### builders
208232
209-
[Netlify On-demand Builders](https://docs.netlify.com/configure-builds/on-demand-builders/) are serverless functions used to build and cache page content on Netlify’s Edge CDN. You can enable these functions with the `builders` option:
233+
You can enable On-demand Builders using the `builders` option:
210234
211235
```js
212236
// astro.config.mjs
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
interface NetlifyLocals {
2+
runtime: {
3+
/**
4+
* On-demand Builders support an optional time to live (TTL) pattern that allows you to set a fixed duration of time after which a cached builder response is invalidated. This allows you to force a refresh of a builder-generated response without a new deploy.
5+
* @param ttl time to live, in seconds
6+
*/
7+
setBuildersTtl(ttl: number): void
8+
}
9+
}

packages/integrations/netlify/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"./package.json": "./package.json"
2727
},
2828
"files": [
29-
"dist"
29+
"dist",
30+
"builders-types.d.ts"
3031
],
3132
"scripts": {
3233
"build": "astro-scripts build \"src/**/*.ts\" && tsc",

packages/integrations/netlify/src/netlify-functions.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,18 +68,28 @@ export const createExports = (manifest: SSRManifest, args: Args) => {
6868
init.body =
6969
typeof requestBody === 'string' ? Buffer.from(requestBody, encoding) : requestBody;
7070
}
71+
7172
const request = new Request(rawUrl, init);
7273

7374
const routeData = app.match(request);
7475
const ip = headers['x-nf-client-connection-ip'];
7576
Reflect.set(request, clientAddressSymbol, ip);
76-
let locals = {};
77+
78+
let locals: Record<string, unknown> = {};
79+
7780
if (request.headers.has(ASTRO_LOCALS_HEADER)) {
7881
let localsAsString = request.headers.get(ASTRO_LOCALS_HEADER);
7982
if (localsAsString) {
8083
locals = JSON.parse(localsAsString);
8184
}
8285
}
86+
87+
let responseTtl = undefined;
88+
89+
locals.runtime = builders
90+
? { setBuildersTtl(ttl: number) { responseTtl = ttl } }
91+
: {}
92+
8393
const response: Response = await app.render(request, routeData, locals);
8494
const responseHeaders = Object.fromEntries(response.headers.entries());
8595

@@ -99,6 +109,7 @@ export const createExports = (manifest: SSRManifest, args: Args) => {
99109
headers: responseHeaders,
100110
body: responseBody,
101111
isBase64Encoded: responseIsBase64Encoded,
112+
ttl: responseTtl,
102113
};
103114

104115
const cookies = response.headers.get('set-cookie');
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { expect } from 'chai';
2+
import { loadFixture, testIntegration } from './test-utils.js';
3+
import netlifyAdapter from '../../dist/index.js';
4+
5+
describe('Builders', () => {
6+
/** @type {import('../../../astro/test/test-utils').Fixture} */
7+
let fixture;
8+
9+
before(async () => {
10+
fixture = await loadFixture({
11+
root: new URL('./fixtures/builders/', import.meta.url).toString(),
12+
output: 'server',
13+
adapter: netlifyAdapter({
14+
dist: new URL('./fixtures/builders/dist/', import.meta.url),
15+
builders: true
16+
}),
17+
site: `http://example.com`,
18+
integrations: [testIntegration()],
19+
});
20+
await fixture.build();
21+
});
22+
23+
it('A route can set builders ttl', async () => {
24+
const entryURL = new URL(
25+
'./fixtures/builders/.netlify/functions-internal/entry.mjs',
26+
import.meta.url
27+
);
28+
const { handler } = await import(entryURL);
29+
const resp = await handler({
30+
httpMethod: 'GET',
31+
headers: {},
32+
rawUrl: 'http://example.com/',
33+
isBase64Encoded: false,
34+
});
35+
expect(resp.ttl).to.equal(45);
36+
});
37+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
Astro.locals.runtime.setBuildersTtl(45)
3+
---
4+
<html>
5+
<head>
6+
<title>Astro on Netlify</title>
7+
</head>
8+
<body>
9+
<h1>{new Date(Date.now())}</h1>
10+
</body>
11+
</html>

0 commit comments

Comments
 (0)