diff --git a/docs/2.deploy/20.providers/netlify.md b/docs/2.deploy/20.providers/netlify.md index 878ab46cd8..5ffb94bc53 100644 --- a/docs/2.deploy/20.providers/netlify.md +++ b/docs/2.deploy/20.providers/netlify.md @@ -46,3 +46,7 @@ Make sure the publish directory is set to `dist` when creating a new project. On-demand Builders are serverless functions used to generate web content as needed that’s automatically cached on Netlify’s Edge CDN. They enable you to build pages for your site when a user visits them for the first time and then cache them at the edge for subsequent visits. :read-more{title="Netlify On-demand Builders" to="https://docs.netlify.com/configure-builds/on-demand-builders/"} + +## Custom deploy configuration + +You can provide additional deploy configuration using the `netlify` key inside `nitro.config`. It will be merged with built-in auto-generated config. Currently the only supported value is `images.remote_images`, for [configuring Netlify Image CDN](https://docs.netlify.com/image-cdn/create-integration/). diff --git a/src/presets/netlify.ts b/src/presets/netlify.ts index 03af1a93f4..5182da9e2e 100644 --- a/src/presets/netlify.ts +++ b/src/presets/netlify.ts @@ -25,6 +25,19 @@ export const netlify = defineNitroPreset({ await writeHeaders(nitro); await writeRedirects(nitro); + if (nitro.options.netlify) { + const configPath = join( + nitro.options.output.dir, + "../deploy/v1/config.json" + ); + await fsp.mkdir(dirname(configPath), { recursive: true }); + await fsp.writeFile( + configPath, + JSON.stringify(nitro.options.netlify), + "utf8" + ); + } + const functionConfig = { config: { nodeModuleFormat: "esm" }, version: 1, diff --git a/src/types/presets/index.ts b/src/types/presets/index.ts index bb817aaa6b..990bb99ffb 100644 --- a/src/types/presets/index.ts +++ b/src/types/presets/index.ts @@ -2,12 +2,14 @@ import { AWSAmplifyOptions } from "./aws-amplify"; import { AzureOptions } from "./azure"; import { CloudflareOptions } from "./cloudflare"; import { FirebaseOptions } from "./firebase"; +import { NetlifyOptions } from "./netlify"; import { VercelOptions } from "./vercel"; export interface PresetOptions { + awsAmplify: AWSAmplifyOptions; azure: AzureOptions; cloudflare: CloudflareOptions; firebase: FirebaseOptions; + netlify: NetlifyOptions; vercel: VercelOptions; - awsAmplify: AWSAmplifyOptions; } diff --git a/src/types/presets/netlify.ts b/src/types/presets/netlify.ts new file mode 100644 index 0000000000..9554a50fe4 --- /dev/null +++ b/src/types/presets/netlify.ts @@ -0,0 +1,12 @@ +/** + * Netlify options + */ +export interface NetlifyOptions { + images?: { + /** + * Permitted remote image sources. Array of regex strings. + * @see https://docs.netlify.com/image-cdn/overview/#remote-path + */ + remote_images?: string[]; + }; +} diff --git a/test/presets/netlify.test.ts b/test/presets/netlify.test.ts index d2a0a50e60..a96eb8a6d9 100644 --- a/test/presets/netlify.test.ts +++ b/test/presets/netlify.test.ts @@ -11,6 +11,11 @@ describe("nitro:preset:netlify", async () => { output: { publicDir: resolve(getPresetTmpDir("netlify"), "dist"), }, + netlify: { + images: { + remote_images: ["https://example.com/.*"], + }, + }, }, }); testNitro( @@ -90,6 +95,20 @@ describe("nitro:preset:netlify", async () => { `); /* eslint-enable no-tabs */ }); + it("should write config.json", async () => { + const config = await fsp + .readFile(resolve(ctx.outDir, "../deploy/v1/config.json"), "utf8") + .then((r) => JSON.parse(r)); + expect(config).toMatchInlineSnapshot(` + { + "images": { + "remote_images": [ + "https://example.com/.*", + ], + }, + } + `); + }); } ); });