Skip to content

Commit 8a82de7

Browse files
authored
feat(netlify): incremental static regeneration + swr (#540)
1 parent 1026edb commit 8a82de7

5 files changed

Lines changed: 68 additions & 0 deletions

File tree

src/presets/netlify.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type { Nitro } from '../types'
66
// Netlify functions
77
export const netlify = defineNitroPreset({
88
extends: 'aws-lambda',
9+
entry: '#internal/nitro/entries/netlify',
910
output: {
1011
dir: '{{ rootDir }}/.netlify/functions-internal',
1112
publicDir: '{{ rootDir }}/dist'
@@ -80,6 +81,11 @@ async function writeRedirects (nitro: Nitro) {
8081
const redirectsPath = join(nitro.options.output.publicDir, '_redirects')
8182
let contents = '/* /.netlify/functions/server 200'
8283

84+
// Rewrite SWR and static paths to builder functions
85+
for (const [key] of Object.entries(nitro.options.routes).filter(([_, value]) => value.swr || value.static)) {
86+
contents = `${key.replace('/**', '/*')}\t/.netlify/builders/server 200\n` + contents
87+
}
88+
8389
for (const [key, value] of Object.entries(nitro.options.routes).filter(([_, value]) => value.redirect)) {
8490
const redirect = typeof value.redirect === 'string' ? { to: value.redirect } : value.redirect
8591
// TODO: update to 307 when netlify support 307/308

src/runtime/entries/netlify.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import '#internal/nitro/virtual/polyfill'
2+
import type { Handler, HandlerResponse, HandlerContext, HandlerEvent } from '@netlify/functions/dist/main'
3+
import type { APIGatewayProxyEventHeaders } from 'aws-lambda'
4+
import { withQuery } from 'ufo'
5+
import { createRouter as createMatcher } from 'radix3'
6+
import { nitroApp } from '../app'
7+
import { useRuntimeConfig } from '../config'
8+
9+
export const handler: Handler = async function handler (event, context) {
10+
const config = useRuntimeConfig()
11+
const routerOptions = createMatcher({ routes: config.nitro.routes })
12+
13+
const query = { ...event.queryStringParameters, ...event.multiValueQueryStringParameters }
14+
const url = withQuery(event.path, query)
15+
const routeOptions = routerOptions.lookup(url) || {}
16+
17+
if (routeOptions.static || routeOptions.swr) {
18+
const builder = await import('@netlify/functions').then(r => r.builder || r.default.builder)
19+
const ttl = typeof routeOptions.swr === 'number' ? routeOptions.swr : 60
20+
const swrHandler = routeOptions.swr
21+
? ((event, context) => lambda(event, context).then(r => ({ ...r, ttl }))) as Handler
22+
: lambda
23+
return builder(swrHandler)(event, context) as any
24+
}
25+
26+
return lambda(event, context)
27+
}
28+
29+
async function lambda (event: HandlerEvent, context: HandlerContext): Promise<HandlerResponse> {
30+
const query = { ...event.queryStringParameters, ...(event).multiValueQueryStringParameters }
31+
const url = withQuery((event).path, query)
32+
const method = (event).httpMethod || 'get'
33+
34+
const r = await nitroApp.localCall({
35+
event,
36+
url,
37+
context,
38+
headers: normalizeIncomingHeaders(event.headers),
39+
method,
40+
query,
41+
body: event.body // TODO: handle event.isBase64Encoded
42+
})
43+
44+
return {
45+
statusCode: r.status,
46+
headers: normalizeOutgoingHeaders(r.headers),
47+
body: r.body.toString()
48+
}
49+
}
50+
51+
function normalizeIncomingHeaders (headers?: APIGatewayProxyEventHeaders) {
52+
return Object.fromEntries(Object.entries(headers || {}).map(([key, value]) => [key.toLowerCase(), value!]))
53+
}
54+
55+
function normalizeOutgoingHeaders (headers: Record<string, string | string[] | undefined>) {
56+
return Object.fromEntries(Object.entries(headers)
57+
.filter(([key]) => !['set-cookie'].includes(key))
58+
.map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v!]))
59+
}

src/types/nitro.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export interface NitroConfig extends DeepPartial<NitroOptions> {
6868

6969
export interface NitroRouteOption {
7070
swr?: boolean | number
71+
static?: boolean
7172
redirect?: string | { to: string, statusCode?: 301 | 302 | 307 | 308 }
7273
headers?: Record<string, string>
7374
cors?: boolean

test/presets/netlify.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ describe('nitro:preset:netlify', async () => {
3737
/rules/nested/* /base 301
3838
/rules/redirect/obj https://nitro.unjs.io/ 308
3939
/rules/redirect /base 301
40+
/rules/static /.netlify/builders/server 200
4041
/* /.netlify/functions/server 200"
4142
`)
4243
/* eslint-enable no-tabs */

test/tests.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export async function setupTest (preset) {
3838
'/rules/headers': { headers: { 'cache-control': 's-maxage=60' } },
3939
'/rules/cors': { cors: true, headers: { 'access-control-allowed-methods': 'GET' } },
4040
'/rules/redirect': { redirect: '/base' },
41+
'/rules/static': { static: true },
4142
'/rules/redirect/obj': {
4243
redirect: { to: 'https://nitro.unjs.io/', statusCode: 308 }
4344
},

0 commit comments

Comments
 (0)