generated from netlify/build-plugin-template
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: netlify Image cdn support (#710)
* feat: image cdn support * fix: package-lock * fix: lint * fix: dependencies * feat: testing * fix: update function * fix: temp remove function * fix: reorder redirects * feat: base url encoded paths * fix: eslint * fix: update redirect * fix: error handling * fix: debug log * fix: path match * fix: more tweaks * fix: split * fix: url param * fix: /functions * fix: param pattern * fix: properly unencode args * fix: use force for redirects * fix: generate __image lambda only if NETLIFY_IMAGE_CDN is set * chore: drop dev/debug logs, skip unnecesary awaits * feat: apply caching headers * fix: add regular cache-control too * chore: use Netlify Image CDN in v5 demo as well * chore: update image-cdn docs * fix: use force for all redirects * fix: update docs * docs: adjust wording for for contentful and drupal to match with wordpress example * Update docs/image-cdn.md Co-authored-by: Michal Piechowiak <[email protected]> --------- Co-authored-by: Michal Piechowiak <[email protected]>
- Loading branch information
Showing
7 changed files
with
225 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,14 @@ | ||
[build] | ||
command = "npm run build" | ||
publish = "public/" | ||
environment = { GATSBY_CLOUD_IMAGE_CDN = "true" } | ||
environment = { NETLIFY_IMAGE_CDN = "true" } | ||
ignore = "if [ $CACHED_COMMIT_REF == $COMMIT_REF ]; then (exit 1); else git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF ..; fi;" | ||
|
||
[[plugins]] | ||
package = "../plugin/src/index.ts" | ||
|
||
[[plugins]] | ||
package = "@netlify/plugin-local-install-core" | ||
|
||
[images] | ||
remote_images = ['https://images.unsplash.com/*'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,14 @@ | ||
[build] | ||
command = "npm run build" | ||
publish = "public/" | ||
environment = { GATSBY_CLOUD_IMAGE_CDN = "true" } | ||
environment = { NETLIFY_IMAGE_CDN = "true" } | ||
ignore = "if [ $CACHED_COMMIT_REF == $COMMIT_REF ]; then (exit 1); else git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF ..; fi;" | ||
|
||
[[plugins]] | ||
package = "../plugin/src/index.ts" | ||
|
||
[[plugins]] | ||
package = "@netlify/plugin-local-install-core" | ||
|
||
[images] | ||
remote_images = ['https://images.unsplash.com/*'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,73 @@ | ||
# Gatsby Image CDN on Netlify | ||
|
||
Gatsby Image CDN is a new feature available in the prerelease version of Gatsby. | ||
Instead of downloading and processing images at build time, it defers processing | ||
until request time. This can greatly improve build times for sites with remote | ||
images, such as those that use a CMS. Netlify includes full support for Image | ||
CDN, on all plans. | ||
|
||
When using the image CDN, Gatsby generates URLs of the form | ||
`/_gatsby/image/...`. On Netlify, these are served by a | ||
[builder function](https://docs.netlify.com/configure-builds/on-demand-builders/), | ||
powered by [sharp](https://sharp.pixelplumbing.com/) and Nuxt's | ||
[ipx image server](https://github.com/unjs/ipx/). It supports all image formats | ||
supported by Gatsby, including AVIF and WebP. | ||
|
||
On first load there will be a one-time delay while the image is resized, but | ||
subsequent requests will be super-fast as they are served from the edge cache. | ||
Gatsby Image CDN is a feature available since Gatsby v4.10.0. Instead of | ||
downloading and processing images at build time, it defers processing until | ||
request time. This can greatly improve build times for sites with remote images, | ||
such as those that use a CMS. Netlify includes full support for Image CDN, on | ||
all plans. | ||
|
||
## Enabling the Image CDN | ||
|
||
To enable the Image CDN during the beta period, you should set the environment | ||
variable `GATSBY_CLOUD_IMAGE_CDN` to `true`. | ||
To enable the Image CDN, you should set the environment variable | ||
`NETLIFY_IMAGE_CDN` to `true`. You will also need to declare allowed image URL | ||
patterns in `netlify.toml`: | ||
|
||
Image CDN currently requires the beta version of Gatsby. This can be installed | ||
using the `next` tag: | ||
```toml | ||
[build.environment] | ||
NETLIFY_IMAGE_CDN = "true" | ||
|
||
```shell | ||
npm install gatsby@next gatsby-plugin-image@next gatsby-plugin-sharp@next gatsby-transformer-sharp@next | ||
[images] | ||
remote_images = [ | ||
'https://example1.com/*', | ||
'https://example2.com/*' | ||
] | ||
``` | ||
|
||
Currently Image CDN supports Contentful and WordPress, and these source plugins | ||
should also be installed using the `next` tag: | ||
Exact URL patterns to use will depend on CMS you use and possibly your | ||
configuration of it. | ||
|
||
```shell | ||
npm install gatsby-source-wordpress@next | ||
``` | ||
- `gatsby-source-contentful`: | ||
|
||
or | ||
```toml | ||
[images] | ||
remote_images = [ | ||
# <your-contentful-space-id> is specified in the `spaceId` option for the | ||
# gatsby-source-contentful plugin in your gatsby-config file. | ||
"https://images.ctfassets.net/<your-contentful-space-id>/*" | ||
] | ||
``` | ||
|
||
```shell | ||
npm install gatsby-source-contentful@next | ||
``` | ||
- `gatsby-source-drupal`: | ||
|
||
```toml | ||
[images] | ||
remote_images = [ | ||
# <your-drupal-base-url> is speciafied in the `baseUrl` option for the | ||
# gatsby-source-drupal plugin in your gatsby-config file. | ||
"<your-drupal-base-url>/*" | ||
] | ||
``` | ||
|
||
- `gatsby-source-wordpress`: | ||
|
||
```toml | ||
[images] | ||
remote_images = [ | ||
# <your-wordpress-url> is specified in the `url` option for the | ||
# gatsby-source-wordpress plugin in your gatsby-config file. | ||
# There is no need to include `/graphql in the path here` | ||
"<your-wordpress-url>/*" | ||
] | ||
``` | ||
|
||
Gatsby will be adding support to more source plugins during the beta period. | ||
These should work automatically as soon as they are added. | ||
Above examples are the most likely ones to be needed. However if you configure | ||
your CMS to host assets on different domain or path, you might need to adjust | ||
the patterns accordingly. | ||
|
||
## Using the Image CDN | ||
## How it works | ||
|
||
Your GraphQL queries will need updating to use the image CDN. The details vary | ||
depending on the source plugin. For more details see | ||
[the Gatsby docs](https://support.gatsbyjs.com/hc/en-us/articles/4522338898579) | ||
When using the Image CDN, Gatsby generates URLs of the form | ||
`/_gatsby/image/...`. On Netlify, these are served by a function that translates | ||
Gatsby Image CDN URLs into Netlify Image CDN compatible URL of the form | ||
`/.netlify/images/...`. For more information about Netlify Image CDN, | ||
documentation can be found [here](https://docs.netlify.com/image-cdn/overview). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
import { Buffer } from 'buffer' | ||
|
||
import { Handler } from '@netlify/functions' | ||
|
||
type Event = Parameters<Handler>[0] | ||
|
||
function generateURLFromQueryParamsPath(uParam, cdParam, argsParam) { | ||
try { | ||
const newURL = new URL('.netlify/images', 'https://example.com') | ||
newURL.searchParams.set('url', uParam) | ||
newURL.searchParams.set('cd', cdParam) | ||
|
||
const aParams = new URLSearchParams(argsParam) | ||
aParams.forEach((value, key) => { | ||
newURL.searchParams.set(key, value) | ||
}) | ||
|
||
return newURL.pathname + newURL.search | ||
} catch (error) { | ||
console.error('Error constructing URL:', error) | ||
return null | ||
} | ||
} | ||
|
||
function generateURLFromBase64EncodedPath(path) { | ||
const [, , , encodedUrl, encodedArgs] = path.split('/') | ||
|
||
const decodedUrl = Buffer.from(encodedUrl, 'base64').toString('utf8') | ||
const decodedArgs = Buffer.from(encodedArgs, 'base64').toString('utf8') | ||
|
||
let sourceURL | ||
try { | ||
sourceURL = new URL(decodedUrl) | ||
} catch (error) { | ||
console.error('Decoded string is not a valid URL:', error) | ||
return | ||
} | ||
|
||
const newURL = new URL('.netlify/images', 'https://example.com') | ||
newURL.searchParams.set('url', sourceURL.href) | ||
|
||
const aParams = new URLSearchParams(decodedArgs) | ||
aParams.forEach((value, key) => { | ||
newURL.searchParams.set(key, value) | ||
}) | ||
|
||
return newURL.pathname + newURL.search | ||
} | ||
|
||
// eslint-disable-next-line require-await | ||
export const handler: Handler = async (event: Event) => { | ||
const QUERY_PARAM_PATTERN = | ||
/^\/\.netlify\/functions\/__image\/image_query_compat\/?$/i | ||
|
||
const { pathname } = new URL(event.rawUrl) | ||
const match = pathname.match(QUERY_PARAM_PATTERN) | ||
|
||
let newURL | ||
|
||
if (match) { | ||
// Extract the query parameters | ||
const { | ||
url: uParam, | ||
cd: cdParam, | ||
args: argsParam, | ||
} = event.queryStringParameters | ||
|
||
newURL = generateURLFromQueryParamsPath(uParam, cdParam, argsParam) | ||
} else { | ||
newURL = generateURLFromBase64EncodedPath(pathname) | ||
} | ||
|
||
const cachingHeaders = { | ||
'Cache-Control': 'public,max-age=31536000,immutable', | ||
'Netlify-CDN-Cache-Control': 'public,max-age=31536000,immutable', | ||
'Netlify-Vary': 'query', | ||
} | ||
|
||
return newURL | ||
? { | ||
statusCode: 301, | ||
headers: { | ||
Location: newURL, | ||
...cachingHeaders, | ||
}, | ||
} | ||
: { statusCode: 400, body: 'Invalid request', headers: cachingHeaders } | ||
} |