Skip to content

Commit ce070d6

Browse files
committed
move setting up remote images config to adapter
1 parent 15e64f2 commit ce070d6

File tree

3 files changed

+94
-71
lines changed

3 files changed

+94
-71
lines changed

src/adapter.ts

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,16 @@
1+
import { mkdir, writeFile } from 'node:fs/promises'
2+
import { dirname } from 'node:path'
3+
14
import type { NextAdapter } from 'next-with-adapters'
5+
import type { RemotePattern } from 'next-with-adapters/dist/shared/lib/image-config.js'
6+
import { makeRe } from 'picomatch'
7+
8+
const NETLIFY_FRAMEWORKS_API_CONFIG_PATH = '.netlify/v1/config.json'
9+
const NETLIFY_IMAGE_LOADER_FILE = '@netlify/plugin-nextjs/dist/next-image-loader.cjs'
10+
11+
function generateRegexFromPattern(pattern: string): string {
12+
return makeRe(pattern).source
13+
}
214

315
const adapter: NextAdapter = {
416
name: 'Netlify',
@@ -10,13 +22,90 @@ const adapter: NextAdapter = {
1022
// Set up Netlify Image CDN image's loaderFile
1123
// see https://nextjs.org/docs/app/api-reference/config/next-config-js/images
1224
config.images.loader = 'custom'
13-
config.images.loaderFile = '@netlify/plugin-nextjs/dist/next-image-loader.cjs'
25+
config.images.loaderFile = NETLIFY_IMAGE_LOADER_FILE
1426
}
1527

1628
return config
1729
},
1830
async onBuildComplete(ctx) {
1931
console.log('onBuildComplete hook called')
32+
33+
let frameworksAPIConfig: any = null
34+
const { images } = ctx.config
35+
if (images.loader === 'custom' && images.loaderFile === NETLIFY_IMAGE_LOADER_FILE) {
36+
const { remotePatterns, domains } = images
37+
// if Netlify image loader is used, configure allowed remote image sources
38+
const remoteImageSources: string[] = []
39+
if (remotePatterns && remotePatterns.length !== 0) {
40+
// convert images.remotePatterns to regexes for Frameworks API
41+
for (const remotePattern of remotePatterns) {
42+
if (remotePattern instanceof URL) {
43+
// Note: even if URL notation is used in next.config.js, This will result in RemotePattern
44+
// object here, so types for the complete config should not have URL as an possible type
45+
throw new TypeError('Received not supported URL instance in remotePatterns')
46+
}
47+
let { protocol, hostname, port, pathname }: RemotePattern = remotePattern
48+
49+
if (pathname) {
50+
pathname = pathname.startsWith('/') ? pathname : `/${pathname}`
51+
}
52+
53+
const combinedRemotePattern = `${protocol ?? 'http?(s)'}://${hostname}${
54+
port ? `:${port}` : ''
55+
}${pathname ?? '/**'}`
56+
57+
try {
58+
remoteImageSources.push(generateRegexFromPattern(combinedRemotePattern))
59+
} catch (error) {
60+
throw new Error(
61+
`Failed to generate Image CDN remote image regex from Next.js remote pattern: ${JSON.stringify(
62+
{ remotePattern, combinedRemotePattern },
63+
null,
64+
2,
65+
)}`,
66+
{
67+
cause: error,
68+
},
69+
)
70+
}
71+
}
72+
}
73+
74+
if (domains && domains.length !== 0) {
75+
for (const domain of domains) {
76+
const patternFromDomain = `http?(s)://${domain}/**`
77+
try {
78+
remoteImageSources.push(generateRegexFromPattern(patternFromDomain))
79+
} catch (error) {
80+
throw new Error(
81+
`Failed to generate Image CDN remote image regex from Next.js domain: ${JSON.stringify(
82+
{ domain, patternFromDomain },
83+
null,
84+
2,
85+
)}`,
86+
{ cause: error },
87+
)
88+
}
89+
}
90+
}
91+
92+
if (remoteImageSources.length !== 0) {
93+
// https://docs.netlify.com/build/frameworks/frameworks-api/#images
94+
frameworksAPIConfig ??= {}
95+
frameworksAPIConfig.images ??= {}
96+
frameworksAPIConfig.images.remote_images = remoteImageSources
97+
}
98+
}
99+
100+
if (frameworksAPIConfig) {
101+
// write out config if there is any
102+
// https://docs.netlify.com/build/frameworks/frameworks-api/#netlifyv1configjson
103+
await mkdir(dirname(NETLIFY_FRAMEWORKS_API_CONFIG_PATH), { recursive: true })
104+
await writeFile(
105+
NETLIFY_FRAMEWORKS_API_CONFIG_PATH,
106+
JSON.stringify(frameworksAPIConfig, null, 2),
107+
)
108+
}
20109
},
21110
}
22111

src/build/image-cdn.ts

Lines changed: 1 addition & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,9 @@
1-
import type { RemotePattern } from 'next/dist/shared/lib/image-config.js'
2-
import { makeRe } from 'picomatch'
3-
41
import { PluginContext } from './plugin-context.js'
52

6-
function generateRegexFromPattern(pattern: string): string {
7-
return makeRe(pattern).source
8-
}
9-
103
/**
114
* Rewrite next/image to netlify image cdn
125
*/
13-
export const setImageConfig = async (ctx: PluginContext): Promise<void> => {
14-
const {
15-
images: { domains, remotePatterns, loader: imageLoader },
16-
} = await ctx.buildConfig
17-
if (imageLoader !== 'default') {
18-
return
19-
}
20-
6+
export const setLegacyIpxRewrite = async (ctx: PluginContext): Promise<void> => {
217
ctx.netlifyConfig.redirects.push(
228
// when migrating from @netlify/plugin-nextjs@4 image redirect to ipx might be cached in the browser
239
{
@@ -30,56 +16,4 @@ export const setImageConfig = async (ctx: PluginContext): Promise<void> => {
3016
status: 200,
3117
},
3218
)
33-
34-
if (remotePatterns?.length !== 0 || domains?.length !== 0) {
35-
ctx.netlifyConfig.images ||= { remote_images: [] }
36-
ctx.netlifyConfig.images.remote_images ||= []
37-
38-
if (remotePatterns && remotePatterns.length !== 0) {
39-
for (const remotePattern of remotePatterns) {
40-
let { protocol, hostname, port, pathname }: RemotePattern = remotePattern
41-
42-
if (pathname) {
43-
pathname = pathname.startsWith('/') ? pathname : `/${pathname}`
44-
}
45-
46-
const combinedRemotePattern = `${protocol ?? 'http?(s)'}://${hostname}${
47-
port ? `:${port}` : ''
48-
}${pathname ?? '/**'}`
49-
50-
try {
51-
ctx.netlifyConfig.images.remote_images.push(
52-
generateRegexFromPattern(combinedRemotePattern),
53-
)
54-
} catch (error) {
55-
ctx.failBuild(
56-
`Failed to generate Image CDN remote image regex from Next.js remote pattern: ${JSON.stringify(
57-
{ remotePattern, combinedRemotePattern },
58-
null,
59-
2,
60-
)}`,
61-
error,
62-
)
63-
}
64-
}
65-
}
66-
67-
if (domains && domains.length !== 0) {
68-
for (const domain of domains) {
69-
const patternFromDomain = `http?(s)://${domain}/**`
70-
try {
71-
ctx.netlifyConfig.images.remote_images.push(generateRegexFromPattern(patternFromDomain))
72-
} catch (error) {
73-
ctx.failBuild(
74-
`Failed to generate Image CDN remote image regex from Next.js domain: ${JSON.stringify(
75-
{ domain, patternFromDomain },
76-
null,
77-
2,
78-
)}`,
79-
error,
80-
)
81-
}
82-
}
83-
}
84-
}
8519
}

src/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
} from './build/content/static.js'
1717
import { clearStaleEdgeHandlers, createEdgeHandlers } from './build/functions/edge.js'
1818
import { clearStaleServerHandlers, createServerHandler } from './build/functions/server.js'
19-
import { setImageConfig } from './build/image-cdn.js'
19+
import { setLegacyIpxRewrite } from './build/image-cdn.js'
2020
import { PluginContext } from './build/plugin-context.js'
2121
import {
2222
verifyAdvancedAPIRoutes,
@@ -88,7 +88,7 @@ export const onBuild = async (options: NetlifyPluginOptions) => {
8888

8989
// static exports only need to be uploaded to the CDN and setup /_next/image handler
9090
if (ctx.buildConfig.output === 'export') {
91-
return Promise.all([copyStaticExport(ctx), setHeadersConfig(ctx), setImageConfig(ctx)])
91+
return Promise.all([copyStaticExport(ctx), setHeadersConfig(ctx), setLegacyIpxRewrite(ctx)])
9292
}
9393

9494
await verifyAdvancedAPIRoutes(ctx)
@@ -101,7 +101,7 @@ export const onBuild = async (options: NetlifyPluginOptions) => {
101101
createServerHandler(ctx),
102102
createEdgeHandlers(ctx),
103103
setHeadersConfig(ctx),
104-
setImageConfig(ctx),
104+
setLegacyIpxRewrite(ctx),
105105
])
106106
})
107107
}

0 commit comments

Comments
 (0)