diff --git a/pages/aws/config/overrides/_meta.json b/pages/aws/config/overrides/_meta.json index b936b59..5e89222 100644 --- a/pages/aws/config/overrides/_meta.json +++ b/pages/aws/config/overrides/_meta.json @@ -7,5 +7,6 @@ "image_loader": "Image Loader", "proxy_external_request": "External Request Proxy", "origin_resolver": "Origin Resolver", - "invoke_function": "Invoke Function for the warmer" + "invoke_function": "Invoke Function for the warmer", + "automatic_cdn_invalidation": "Automatic CDN Invalidation" } \ No newline at end of file diff --git a/pages/aws/config/overrides/automatic_cdn_invalidation.mdx b/pages/aws/config/overrides/automatic_cdn_invalidation.mdx new file mode 100644 index 0000000..268c8dc --- /dev/null +++ b/pages/aws/config/overrides/automatic_cdn_invalidation.mdx @@ -0,0 +1,34 @@ +import {Callout} from 'nextra/components' + +Available since `@opennextjs/aws` 3.4.0 + +This override is used by OpenNext when routes have been On-Demand revalidated and the CDN needs to be updated. It is not called for ISR revalidation. +It will be called for `revalidatePath`, `revalidateTag` and `res.revalidate()`. + +If you want to better understand how to implement your own Automatic CDN Invalidation, the easiest way would be to take a look at the existing [included Automatic CDN Invalidation](https://github.com/opennextjs/opennextjs-aws/tree/main/packages/open-next/src/overrides/cdnInvalidation) + +## Included Automatic CDN Invalidation + +### dummy + +The Dummy AutomaticCDNInvalidation is a dummy implementation that will do nothing. It is the default implementation. + +### cloudfront + + +Cloudfront invalidation can be very expensive. Manual CloudFront path invalidation incurs costs. According to the [AWS CloudFront pricing page](https://aws.amazon.com/cloudfront/pricing/): + +> No additional charge for the first 1,000 paths requested for invalidation each month. Thereafter, $0.005 per path requested for invalidation. + +This implementation will send an invalidation request for every route that needs to be invalidated (the request invalidates 2 paths, one for the route itself and one for the data route). + +Tag cache invalidation could end up triggering thousands of invalidation requests. + +Only use this implementation if you are aware of the costs and are willing to pay for it. + + +The CloudFront Automatic CDN Invalidation will invalidate the cache of the CloudFront distribution. + +##### Requirements + +- You need to provide the `CLOUDFRONT_DISTRIBUTION_ID` environment variable to your server. \ No newline at end of file diff --git a/pages/aws/config/overrides/image_loader.mdx b/pages/aws/config/overrides/image_loader.mdx index 459b14c..2520cdf 100644 --- a/pages/aws/config/overrides/image_loader.mdx +++ b/pages/aws/config/overrides/image_loader.mdx @@ -2,4 +2,32 @@ This override is used by OpenNext and more specifically by the image server to b This is used for internal image only (i.e. src without host). External source are already handled by the image server. -If you want to better understand how to implement your own ImageLoader, the easiest way would be to take a look at the existing [included ImageLoader](https://github.com/opennextjs/opennextjs-aws/tree/main/packages/open-next/src/overrides/imageLoader). \ No newline at end of file +If you want to better understand how to implement your own ImageLoader, the easiest way would be to take a look at the existing [included ImageLoader](https://github.com/opennextjs/opennextjs-aws/tree/main/packages/open-next/src/overrides/imageLoader). + +## Included ImageLoader + +### s3 + +The S3 ImageLoader will load images from an S3 bucket. It is used by default if you don't provide any ImageLoader in your configuration. +It uses the `@aws-sdk/client-s3` to interact with S3. + +##### Requirements + +- You need to provide the `BUCKET_KEY_PREFIX`, `BUCKET_NAME` environment variables to your server. + +### host + +The Host ImageLoader will load images from the host. +This implementation will directly fetch the image from the host. + +##### Requirements + +The host should be provided in the headers of the request, either in the `X-Forwarded-Host` or the `Host` header. + +### fs-dev + +The FsDev ImageLoader is a simple implementation that loads images from the `.open-next/assets` folder and interact with it using the filesystem. It is useful for development purposes only. + +### dummy + +The Dummy ImageLoader is a dummy implementation that will throw an exception. It should not be used. \ No newline at end of file diff --git a/pages/aws/config/overrides/incremental_cache.mdx b/pages/aws/config/overrides/incremental_cache.mdx index de287ce..0e9ca3d 100644 --- a/pages/aws/config/overrides/incremental_cache.mdx +++ b/pages/aws/config/overrides/incremental_cache.mdx @@ -1,3 +1,5 @@ +import { Callout } from 'nextra/components'; + This override is used by the [`cache` adapter](https://github.com/opennextjs/opennextjs-aws/blob/main/packages/open-next/src/adapters/cache.ts) that is provided by OpenNext to the NextServer. It is also used by OpenNext if `enableCacheInterception` is set to `true` in the configuration. It is used for retrieving and updating the ISR/SSG cache as well as the fetch cache used by Next.js. It does not handle anything related to cache tags (i.e. `revalidateTag` and `revalidatePath`) @@ -5,4 +7,65 @@ It is used for retrieving and updating the ISR/SSG cache as well as the fetch ca If you want to better understand how to implement your own IncrementalCache, the easiest way would be to take a look at the existing [included IncrementalCache](https://github.com/opennextjs/opennextjs-aws/tree/main/packages/open-next/src/overrides/incrementalCache). One thing to note is that it is not used at build time, only at runtime. This means that you'll have to upload the cache yourself if you want to use the prebuilt routes/pages (And this is mandatory for ISR/SSG routes with `fallback:false`). -All the cache files are present in the `.open-next/cache` folder. The one under `BUILD_ID` are for the ISR/SSG cache and the one under `__fetch/BUILD_ID` are for the fetch cache. \ No newline at end of file +All the cache files are present in the `.open-next/cache` folder. The one under `BUILD_ID` are for the ISR/SSG cache and the one under `__fetch/BUILD_ID` are for the fetch cache. + +## Included IncrementalCache + +### s3 + +The S3 IncrementalCache will store fetch and ISR/SSG cache in an S3 bucket. It is used by default if you don't provide any IncrementalCache in your configuration. +It uses the `@aws-sdk/client-s3` to interact with S3. + +##### Requirements + +- You need to provide the `CACHE_BUCKET_REGION`, `CACHE_BUCKET_KEY_PREFIX`, `CACHE_BUCKET_NAME` environment variables to your server. + +### s3-lite + +The S3Lite IncrementalCache will store fetch and ISR/SSG cache in an S3 bucket +This implementation is a lighter version of the `s3` IncrementalCache as it uses `aws4fetch` to interact with S3. + +##### Requirements + +- You need to provide the `CACHE_BUCKET_REGION`, `CACHE_BUCKET_KEY_PREFIX`, `CACHE_BUCKET_NAME`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN` environment variables to your server. + +### dummy + +The Dummy IncrementalCache is a dummy implementation that throws exceptions for every method. It should not be used unless you want to disable the cache. + +### fs-dev + +The FsDev IncrementalCache is a simple implementation that stores the cache in the `.open-next/cache` folder and interact with it using the filesystem. It is useful for development purposes only. + +##### Requirements + +It needs to run on the local filesystem, and expect to be run from the OpenNext output. + +### multi-tier-ddb-s3 + + +Because of how it works, this implementation is only eventually consistent and errors during update could cause inconsistencies between the local cache in some instances and the S3 cache. + +It could also end up being slower (and more expensive) than simpler implementations if used in a low traffic serverless environment. + + +The MultiTierDdbS3 IncrementalCache is a more complex implementation. +Cache are stored in a local In Memory LRU cache as well as in S3. DynamoDB is used to keep local cache in sync between multiple instances of the server. +It uses `aws4fetch` to interact with S3 and DynamoDB. + +##### How does it work + +On get : +1. When a cache is requested, it first checks the local cache. If the cache is not found, it will fetch it from S3 and store it in the local cache. +2. If the local cache exist for this key, it will check metadata in DynamoDB to see if the cache is still valid. If it is not, it will fetch the cache from S3 and store it in the local cache. +3. If the local cache is valid, it will return it. + +On update : +1. When a cache is updated, it will first try to update the cache in S3. +2. If the update is successful, it will update the metadata in DynamoDB. +3. Finally, it will update the local cache. + +##### Requirements + +- You need to provide the `CACHE_BUCKET_REGION`, `CACHE_BUCKET_KEY_PREFIX`, `CACHE_BUCKET_NAME`, `CACHE_DYNAMO_TABLE`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN`, `OPEN_NEXT_LOCAL_CACHE_TTL_MS` (optional), `OPEN_NEXT_LOCAL_CACHE_SIZE`(optional) environment variables to your server. +- DynamoDB table should have a primary key `path` of type `String` and a sort key `tag` of type `String`. (It can reuse the same table as the one used by the default `tagCache`) \ No newline at end of file diff --git a/pages/aws/config/overrides/queue.mdx b/pages/aws/config/overrides/queue.mdx index 359ac8c..c081ca3 100644 --- a/pages/aws/config/overrides/queue.mdx +++ b/pages/aws/config/overrides/queue.mdx @@ -1,3 +1,5 @@ +import {Callout} from 'nextra/components' + This override is used by OpenNext to trigger the revalidation of stale routes. Before sending the response to the client, OpenNext will check if the route is stale and if it is, it will call the queue override to revalidate the route. @@ -5,4 +7,36 @@ If you want to better understand how to implement your own Queue, the easiest wa Couple of things to note : - The default implementation use an SQS queue. This has the main advantage of being able to control the concurrency of the revalidations as well as avoiding trigerring the revalidation multiple times for the same route. -- You don't have to use a queue at all. You could trigger the revalidation directly in the Queue override itself. You can see a very simple implementation of this [in the `queue` override](/aws/contribute/local_run#devqueuets). \ No newline at end of file +- You don't have to use a queue at all. You could trigger the revalidation directly in the Queue override itself. You can see a very simple implementation of this [in the `queue` override](/aws/contribute/local_run#devqueuets). + +## Included Queue + +### sqs + +The SQS Queue will send a message to an SQS queue for each route that needs to be revalidated. It is used by default if you don't provide any Queue in your configuration. +It uses the `@aws-sdk/client-sqs` to interact with SQS. + +#### Requirements + +- You need to provide the `QUEUE_URL` environment variable to your server. + +### sqs-lite + +The SQSLite Queue will send a message to an SQS queue for each route that needs to be revalidated. +This implementation is a lighter version of the `sqs` Queue as it uses `aws4fetch` to interact with SQS. + +#### Requirements + +- You need to provide the `QUEUE_URL`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN` environment variables to your server. + +### direct + +The Direct Queue will directly trigger the revalidation of the route in the Queue override itself. It is useful for development purposes and when you don't want to use a queue. + + +Be careful with this implementation as it could lead to multiple revalidations of the same route. + + +### dummy + +The Dummy Queue is a dummy implementation that will throw an exception. It should not be used unless you want to disable ISR. \ No newline at end of file diff --git a/pages/aws/config/overrides/tag_cache.mdx b/pages/aws/config/overrides/tag_cache.mdx index 7cd278a..47575dd 100644 --- a/pages/aws/config/overrides/tag_cache.mdx +++ b/pages/aws/config/overrides/tag_cache.mdx @@ -9,4 +9,34 @@ This also has the drawback of needing to prepopulate the cache with all the tags We might allow to choose how cache tags are handled in the future depending on the `TagCache` implementation. If you need this feature, feel free to open an issue on the [GitHub repository](https://github.com/opennextjs/opennextjs-aws). -If you want to better understand how to implement your own TagCache, the easiest way would be to take a look at the existing [included TagCache](https://github.com/opennextjs/opennextjs-aws/tree/main/packages/open-next/src/overrides/tagCache). \ No newline at end of file +If you want to better understand how to implement your own TagCache, the easiest way would be to take a look at the existing [included TagCache](https://github.com/opennextjs/opennextjs-aws/tree/main/packages/open-next/src/overrides/tagCache). + +## Included TagCache + +### dynamodb + +The DynamoDB TagCache will store the cache tags in a DynamoDB table. It is used by default if you don't provide any TagCache in your configuration. +It uses the `@aws-sdk/client-dynamodb` to interact with DynamoDB. + +##### Requirements + +- You need to provide the `CACHE_DYNAMO_TABLE`, `CACHE_BUCKET_REGION` environment variable to your server. +- DynamoDB table should have a primary key `path` of type `String` and a sort key `tag` of type `String`. (It can reuse the same table as the one used by the default `tagCache`) + +### dynamodb-lite + +The DynamoDBLite TagCache will store the cache tags in a DynamoDB table. +This implementation is a lighter version of the `dynamodb` TagCache as it uses `aws4fetch` to interact with DynamoDB. + +##### Requirements + +- You need to provide the `CACHE_DYNAMO_TABLE`, `CACHE_BUCKET_REGION`, `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_SESSION_TOKEN` environment variables to your server. +- DynamoDB table should have a primary key `path` of type `String` and a sort key `tag` of type `String`. (It can reuse the same table as the one used by the default `tagCache`) + +### dummy + +The Dummy TagCache is a dummy implementation that will throw an exception. It should not be used unless you want to disable cache tags. + +### fs-dev + +The FsDev TagCache is a simple implementation that stores the cache tags in a file in the `.open-next/cache` folder and interact with it using the filesystem. It is useful for development purposes only. \ No newline at end of file diff --git a/pages/aws/config/overrides/wrapper.mdx b/pages/aws/config/overrides/wrapper.mdx index 913edcb..3d81b00 100644 --- a/pages/aws/config/overrides/wrapper.mdx +++ b/pages/aws/config/overrides/wrapper.mdx @@ -7,3 +7,62 @@ Couple of things to note : - If you don't use streaming (like in the default `aws-lambda` wrapper), you may still need to provide a fake `StreamCreator` to the `handler` to avoid a weird issue with Node itself (see [here](https://github.com/opennextjs/opennextjs-aws/blob/f685ddea8f8a5c82591dc02713aff7138f2d9896/packages/open-next/src/overrides/wrappers/aws-lambda.ts#L49-L65) for an example and a more thorough explanation). - If you use the `edge` runtime of Next (either for the external middleware or for an `edge` route or page), you don't need the `StreamCreator` at all. - If you are in a serverless environment and it supports `waitUntil`, you should define it here(see [here](https://github.com/opennextjs/opennextjs-aws/blob/f685ddea8f8a5c82591dc02713aff7138f2d9896/packages/open-next/src/overrides/wrappers/cloudflare.ts#L29)). This might not be necessary depending on where you run it (for example the `aws-lambda-streaming` or the `node` wrapper doesn't need it.) + + +## Included Wrappers + +### aws-lambda + +The `aws-lambda` Wrapper is the default wrapper for AWS Lambda. It is used by default if you don't provide any Wrapper in your configuration. + +#### Features +- [ ] Streaming +- [ ] Proper support for `waitUntil` + +### aws-lambda-streaming + +The `aws-lambda-streaming` Wrapper is a wrapper that allows you to use streaming in AWS Lambda. Streaming must be enabled for this lambda. + +#### Features +- [x] Streaming +- [x] Proper support for `waitUntil` + +### cloudflare-edge + +The `cloudflare-edge` Wrapper is the wrapper for Cloudflare Workers. It should be used for the external middleware and for the `edge` runtime of Next. + +#### Features +- [x] Streaming +- [x] Proper support for `waitUntil` + +### cloudflare-node + +The `cloudflare-node` Wrapper is the wrapper for Cloudflare Workers. It should be used only with the `node` runtime of Next and if you use `@opennextjs/cloudflare`. + +#### Features +- [x] Streaming +- [x] Proper support for `waitUntil` + +### node + +The `node` Wrapper is the wrapper for classic Node.js Server. This one is a long running server. + +#### Features +- [x] Streaming +- [x] Proper support for `waitUntil` + +### express-dev + +The `express-dev` Wrapper is the wrapper for a classic Express server. It is a long running process and should be used for development purposes only. + +#### Features +- [x] Streaming +- [x] Proper support for `waitUntil` + +### dummy + +The `dummy` Wrapper is a dummy implementation that will just forward the event and `StreamCreator` to the handler. + +#### Features +- [ ] Streaming +- [ ] Proper support for `waitUntil` \ No newline at end of file diff --git a/pages/aws/contribute/local_run.mdx b/pages/aws/contribute/local_run.mdx index ec058eb..970a2cf 100644 --- a/pages/aws/contribute/local_run.mdx +++ b/pages/aws/contribute/local_run.mdx @@ -1,4 +1,5 @@ -When making some changes to OpenNext, it can be a bit cumbersome to need to deploy every time you want to test changes. If your change is not dependent on the wrapper or the converter, then you can create a custom `open-next.config.ts` file (you can use another name so that it doesn't conflict with your existing `open-next.config.ts`). Here is an example with a bunch of custom overrides: +When making some changes to OpenNext, it can be a bit cumbersome to need to deploy every time you want to test changes. If your change is not dependent on the wrapper or the converter, then you can create a custom `open-next.config.ts` file (you can use another name so that it doesn't conflict with your existing `open-next.config.ts`). + To run `OpenNext` locally: ```bash @@ -12,179 +13,36 @@ node .open-next/server-functions/default/index.mjs // open-next.local.config.ts - // A good practice would be to use a different name so that it doesn't conflict // with your existing open-next.config.ts i.e. open-next.local.config.ts -import type {OpenNextConfig} from '@opennextjs/aws/types/open-next.js' +// You could also customize it by using custom overrides if you need to. +import type {OpenNextConfig} from "@opennextjs/aws/types/open-next.js" -const config = { +export default { default: { - override:{ - // We use a custom wrapper so that we can use static assets and image optimization locally - wrapper: () => import('./dev/wrapper').then(m => m.default), - // ISR and SSG won't work properly locally without this - Remove if you only need SSR - incrementalCache: () => import('./dev/incrementalCache').then(m => m.default), - // ISR requires a queue to work properly - Remove if you only need SSR or SSG - queue: () => import('./dev/queue').then(m => m.default), - converter: 'node', + override: { + wrapper: "express-dev", + converter: "node", + incrementalCache: "fs-dev", + queue: "direct", + tagCache: "fs-dev" } }, - // You don't need this part if you don't use image optimization or don't need it in your test + imageOptimization: { - // Image optimization only work on linux, and you have to use the correct architecture for your system - arch: 'x64', override: { - wrapper: 'node', - converter: 'node', + wrapper: "dummy", + converter: "dummy" + }, + loader: "fs-dev", + // This part is not needed on arm linux, and image optimization will only work in linux + install: { + arch: "x64", + packages: ["sharp"] } - // If you need to test with local assets, you'll have to override the imageLoader as well }, - dangerous: { - // We disable the cache tags as it will usually not be needed locally for testing - // It's only used for next/cache revalidateTag and revalidatePath - // If you need it you'll have to override the tagCache as well - disableTagCache: true, - - - // You can uncomment this line if you only need to test SSR - //disableIncrementalCache: true, - }, - // You can override the build command so that you don't have to rebuild the app every time - // You need to have run the default build command at least once - buildCommand: 'echo "no build command"', - edgeExternals: ['./dev/wrapper', './dev/incrementalCache', './dev/queue'], + // You can override the build command here so that you don't have to rebuild next every time you make a change + //buildCommand: "echo 'No build command'", } satisfies OpenNextConfig export default config ``` - -### dev/wrapper.ts - -```typescript -// dev/wrapper.ts -// You'll need to install express -import express from 'express' -// The proxy is used to proxy the image optimization server -// you don't have to use it, but image request will return 500 error -import proxy from 'express-http-proxy' -import { fork } from 'child_process' - -import type { StreamCreator } from "@opennextjs/aws/types/open-next.js"; -import type { WrapperHandler } from "@opennextjs/aws/types/overrides.js"; - -const wrapper: WrapperHandler = async (handler, converter) => { - const app = express(); - // To serve static assets - app.use(express.static('../../assets')) - - //Launch image server fork - fork('../../image-optimization-function/index.mjs', [], { - env: { - NODE_ENV: 'development', - PORT: '3001', - } - }) - app.use('/_next/image', proxy('localhost:3001')) - - app.all('*', async (req, res) => { - const internalEvent = await converter.convertFrom(req); - const _res : StreamCreator = { - writeHeaders: (prelude) => { - res.writeHead(prelude.statusCode, prelude.headers); - res.uncork(); - return res; - }, - onFinish: () => { - // Is it necessary to do something here? - }, - }; - await handler(internalEvent, _res); - }); - - const server = app.listen(parseInt(process.env.PORT ?? "3000", 10), ()=> { - console.log(`Server running on port ${process.env.PORT ?? 3000}`); - }) - - - app.on('error', (err) => { - console.error('error', err); - }); - - return () => { - server.close(); - }; -}; - -export default { - wrapper, - name: "dev-node", - supportStreaming: true, -}; -``` - -### dev/incrementalCache.ts -```typescript -// dev/incrementalCache.ts -import type { IncrementalCache } from '@opennextjs/aws/types/overrides.js' - -import fs from 'node:fs/promises' -import path from 'node:path' - -const buildId = process.env.NEXT_BUILD_ID -const basePath= path.resolve(process.cwd(), `../../cache/${buildId}`) - -const getCacheKey = (key: string) => { - return path.join(basePath, `${key}.cache`) -} - -const cache: IncrementalCache = { - name: 'dev-fs', - get: async (key: string) => { - const fileData = await fs.readFile(getCacheKey(key), 'utf-8') - const data = JSON.parse(fileData) - const {mtime} = await fs.stat(getCacheKey(key)) - return { - value: data, - lastModified: mtime.getTime(), - } - }, - set: async (key, value, isFetch) => { - const data = JSON.stringify(value) - await fs.writeFile(getCacheKey(key), data) - }, - delete: async (key) => { - await fs.rm(getCacheKey(key)) - } -} - -export default cache -``` - -### dev/queue.ts - -```typescript -// dev/queue.ts -import type { Queue } from '@opennextjs/aws/types/overrides.js' - -declare global { - // This is declared in the global scope so that we can use it in the queue - // We need to use this one as next overrides the global fetch - var internalFetch: typeof fetch -} - -const queue: Queue = { - name: 'dev-queue', - send: async (message) => { - const prerenderManifest = (await import('@opennextjs/aws/adapters/config.js')).PrerenderManifest as any - const revalidateId : string = prerenderManifest.preview.previewModeId - await globalThis.internalFetch(`http://localhost:3000${message.MessageBody.url}`, { - method: "HEAD", - headers: { - "x-prerender-revalidate": revalidateId, - "x-isr": "1", - }, - },) - console.log('sending message', message) - }, -} - -export default queue -```