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>
This commit is contained in:
parent
6a9fb2d95d
commit
f974c95a27
7 changed files with 116 additions and 3 deletions
20
.changeset/sweet-cows-roll.md
Normal file
20
.changeset/sweet-cows-roll.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
'@astrojs/netlify': minor
|
||||
---
|
||||
|
||||
If you are using Netlify's On-demand Builders, you can now specify how long your pages should remain cached. By default, all pages will be rendered on first visit and reused on every subsequent visit until a redeploy. To set a custom revalidation time, call the `runtime.setBuildersTtl()` local in either your frontmatter or middleware.
|
||||
|
||||
```astro
|
||||
---
|
||||
import Layout from '../components/Layout.astro'
|
||||
|
||||
if (import.meta.env.PROD) {
|
||||
// revalidates every 45 seconds
|
||||
Astro.locals.runtime.setBuildersTtl(45)
|
||||
}
|
||||
---
|
||||
<Layout title="Astro on Netlify">
|
||||
{new Date(Date.now())}
|
||||
</Layout>
|
||||
```
|
||||
|
|
@ -162,6 +162,30 @@ Once you run `astro build` there will be a `dist/_redirects` file. Netlify will
|
|||
> **Note**
|
||||
> 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.
|
||||
|
||||
### On-demand Builders
|
||||
|
||||
[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).
|
||||
|
||||
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).
|
||||
|
||||
As an example, for the following snippet, Netlify will store the rendered HTML for 45 seconds.
|
||||
|
||||
```astro
|
||||
---
|
||||
import Layout from '../components/Layout.astro';
|
||||
|
||||
if (import.meta.env.PROD) {
|
||||
Astro.locals.runtime.setBuildersTtl(45);
|
||||
}
|
||||
---
|
||||
|
||||
<Layout title="Astro on Netlify">
|
||||
{new Date(Date.now())}
|
||||
</Layout>
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Usage
|
||||
|
||||
[Read the full deployment guide here.](https://docs.astro.build/en/guides/deploy/netlify/)
|
||||
|
@ -206,7 +230,7 @@ directory = "dist/functions"
|
|||
|
||||
### builders
|
||||
|
||||
[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:
|
||||
You can enable On-demand Builders using the `builders` option:
|
||||
|
||||
```js
|
||||
// astro.config.mjs
|
||||
|
|
9
packages/integrations/netlify/builders-types.d.ts
vendored
Normal file
9
packages/integrations/netlify/builders-types.d.ts
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
interface NetlifyLocals {
|
||||
runtime: {
|
||||
/**
|
||||
* 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.
|
||||
* @param ttl time to live, in seconds
|
||||
*/
|
||||
setBuildersTtl(ttl: number): void
|
||||
}
|
||||
}
|
|
@ -26,7 +26,8 @@
|
|||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"builders-types.d.ts"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "astro-scripts build \"src/**/*.ts\" && tsc",
|
||||
|
|
|
@ -68,18 +68,28 @@ export const createExports = (manifest: SSRManifest, args: Args) => {
|
|||
init.body =
|
||||
typeof requestBody === 'string' ? Buffer.from(requestBody, encoding) : requestBody;
|
||||
}
|
||||
|
||||
const request = new Request(rawUrl, init);
|
||||
|
||||
const routeData = app.match(request);
|
||||
const ip = headers['x-nf-client-connection-ip'];
|
||||
Reflect.set(request, clientAddressSymbol, ip);
|
||||
let locals = {};
|
||||
|
||||
let locals: Record<string, unknown> = {};
|
||||
|
||||
if (request.headers.has(ASTRO_LOCALS_HEADER)) {
|
||||
let localsAsString = request.headers.get(ASTRO_LOCALS_HEADER);
|
||||
if (localsAsString) {
|
||||
locals = JSON.parse(localsAsString);
|
||||
}
|
||||
}
|
||||
|
||||
let responseTtl = undefined;
|
||||
|
||||
locals.runtime = builders
|
||||
? { setBuildersTtl(ttl: number) { responseTtl = ttl } }
|
||||
: {}
|
||||
|
||||
const response: Response = await app.render(request, routeData, locals);
|
||||
const responseHeaders = Object.fromEntries(response.headers.entries());
|
||||
|
||||
|
@ -99,6 +109,7 @@ export const createExports = (manifest: SSRManifest, args: Args) => {
|
|||
headers: responseHeaders,
|
||||
body: responseBody,
|
||||
isBase64Encoded: responseIsBase64Encoded,
|
||||
ttl: responseTtl,
|
||||
};
|
||||
|
||||
const cookies = response.headers.get('set-cookie');
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
import { expect } from 'chai';
|
||||
import { loadFixture, testIntegration } from './test-utils.js';
|
||||
import netlifyAdapter from '../../dist/index.js';
|
||||
|
||||
describe('Builders', () => {
|
||||
/** @type {import('../../../astro/test/test-utils').Fixture} */
|
||||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: new URL('./fixtures/builders/', import.meta.url).toString(),
|
||||
output: 'server',
|
||||
adapter: netlifyAdapter({
|
||||
dist: new URL('./fixtures/builders/dist/', import.meta.url),
|
||||
builders: true
|
||||
}),
|
||||
site: `http://example.com`,
|
||||
integrations: [testIntegration()],
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
it('A route can set builders ttl', async () => {
|
||||
const entryURL = new URL(
|
||||
'./fixtures/builders/.netlify/functions-internal/entry.mjs',
|
||||
import.meta.url
|
||||
);
|
||||
const { handler } = await import(entryURL);
|
||||
const resp = await handler({
|
||||
httpMethod: 'GET',
|
||||
headers: {},
|
||||
rawUrl: 'http://example.com/',
|
||||
isBase64Encoded: false,
|
||||
});
|
||||
expect(resp.ttl).to.equal(45);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
---
|
||||
Astro.locals.runtime.setBuildersTtl(45)
|
||||
---
|
||||
<html>
|
||||
<head>
|
||||
<title>Astro on Netlify</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>{new Date(Date.now())}</h1>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue