Skip to content

Commit

Permalink
feat: next.js ISR support
Browse files Browse the repository at this point in the history
  • Loading branch information
adamdottv committed Jun 15, 2021
1 parent 2e5f41e commit 2bda454
Show file tree
Hide file tree
Showing 5 changed files with 928 additions and 539 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@
"@aws-sdk/client-s3": "^3.8.1",
"@aws-sdk/client-sts": "^3.8.1",
"@aws-sdk/credential-provider-node": "^3.8.0",
"@sls-next/lambda-at-edge": "^1.8.0-alpha.42",
"@sls-next/lambda-at-edge": "^1.9.0-alpha.22",
"archiver": "^5.3.0",
"chalk": "4.1.0",
"cheerio": "^1.0.0-rc.5",
Expand Down
3 changes: 3 additions & 0 deletions src/components/Deploy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ export const Deploy: React.FunctionComponent = () => {
NextJsBasePath: isNextJs ? nextBuildLocal?.basePath : undefined,
NextJsStaticPath: isNextJs ? nextBuildLocal?.staticPath : undefined,
NextJsApiPath: isNextJs ? nextBuildLocal?.apiPath : undefined,
NextJsRegenerationLambdaKey: isNextJs
? getLambdaPath(nextBuildLocal?.regenerationLambdaPath)
: undefined,
})

const outputs = await deployStack({stack, credentials})
Expand Down
26 changes: 23 additions & 3 deletions src/utils/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as path from 'path'

import archiver from 'archiver'
import * as glob from 'glob'
import {Builder} from '@sls-next/lambda-at-edge'
import {Builder, PreRenderedManifest} from '@sls-next/lambda-at-edge'

const nextBuildDir = '.ness/.next'
const nextLambdaDir = '.ness/.next/__lambdas'
Expand Down Expand Up @@ -199,8 +199,10 @@ export const reduceInvalidationPaths = (invalidationPaths: string[]): string[] =
}

const dynamicPathToInvalidationPath = (dynamicPath: string) => {
const [firstSegment] = dynamicPath.split('/:')
return path.join(firstSegment || '/', '*')
const [base] = dynamicPath.split('/:')
const [firstSegment] = base.split('/[')
// Ensure this is posix path as CloudFront needs forward slash in invalidation
return path.posix.join(firstSegment || '/', '*')
}

export const readInvalidationPathsFromManifest = (
Expand Down Expand Up @@ -246,6 +248,10 @@ const getNextRoutesManifest = async (): Promise<RoutesManifest | undefined> => {
return readJsonFile(path.join(nextBuildDir, 'default-lambda/routes-manifest.json'))
}

const getNextPrerenderManifest = async (): Promise<PreRenderedManifest | undefined> => {
return readJsonFile(path.join(nextBuildDir, 'default-lambda/prerender-manifest.json'))
}

function zipDirectory(directory: string, outputFile: string): Promise<string> {
return new Promise(async (resolve, reject) => {
// The below options are needed to support following symlinks when building zip files:
Expand Down Expand Up @@ -298,6 +304,7 @@ export type NextBuild = {
defaultLambdaPath: string
imageLambdaPath?: string
apiLambdaPath?: string
regenerationLambdaPath?: string
assets: CacheConfig
basePath: string
staticPath: string
Expand All @@ -319,11 +326,13 @@ export const buildNextApp = async (entry: string = process.cwd()): Promise<NextB
apiBuildManifest,
imageBuildManifest,
routesManifest,
prerenderManifest,
] = await Promise.all([
getNextDefaultManifest(),
getNextApiBuildManifest(),
getNextImageBuildManifest(),
getNextRoutesManifest(),
getNextPrerenderManifest(),
])

const lambdaBuildDir = path.resolve(entry, nextLambdaDir)
Expand All @@ -346,6 +355,7 @@ export const buildNextApp = async (entry: string = process.cwd()): Promise<NextB
const defaultLambdaPath = await zipLambda('default-lambda')
let apiLambdaPath = undefined
let imageLambdaPath = undefined
let regenerationLambdaPath = undefined

const apis = apiBuildManifest?.apis
const hasApi =
Expand All @@ -359,6 +369,15 @@ export const buildNextApp = async (entry: string = process.cwd()): Promise<NextB
imageLambdaPath = await zipLambda('image-lambda')
}

const hasISRPages =
prerenderManifest &&
Object.keys(prerenderManifest.routes).some(
(key) => typeof prerenderManifest.routes[key].initialRevalidateSeconds === 'number',
)
if (hasISRPages) {
regenerationLambdaPath = await zipLambda('regeneration-lambda')
}

const assetsDir = path.join(buildDir, 'assets')
const assets = readAssetsDirectory(assetsDir)

Expand All @@ -367,6 +386,7 @@ export const buildNextApp = async (entry: string = process.cwd()): Promise<NextB
defaultLambdaPath,
apiLambdaPath,
imageLambdaPath,
regenerationLambdaPath,
assets,
imagePath: hasImages ? pathPattern('_next/image*') : undefined,
dataPath: pathPattern('_next/data/*'),
Expand Down
117 changes: 115 additions & 2 deletions static/stacks/web.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ Parameters:
Description: 'Next.js api lambda key.'
Type: String
Default: ''
NextJsRegenerationLambdaKey:
Description: 'Next.js regeneration lambda key.'
Type: String
Default: ''
NextJsImagePath:
Description: 'Next.js image path.'
Type: String
Expand Down Expand Up @@ -127,6 +131,7 @@ Conditions:
HasNextJsBasePath: !Not [!Equals [!Ref NextJsBasePath, '']]
HasNextJsStaticPath: !Not [!Equals [!Ref NextJsStaticPath, '']]
HasNextJsApiPath: !Not [!Equals [!Ref NextJsApiPath, '']]
HasNextJsRegenerationLambda: !Not [!Equals [!Ref NextJsRegenerationLambdaKey, '']]

Resources:
Bucket:
Expand Down Expand Up @@ -201,9 +206,39 @@ Resources:
Statement:
- Effect: Allow
Action:
- s3:GetObject
- s3:PutObject
- s3:GetObject*
- s3:GetBucket*
- s3:List*
- s3:DeleteObject*
- s3:PutObject*
- s3:Abort*
Resource: !Sub 'arn:aws:s3:::${Bucket}/*'
- PolicyName: SQSPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- sqs:SendMessage
- sqs:GetQueueAttributes
- sqs:GetQueueUrl
Resource:
- !If
- HasNextJsRegenerationLambda
- !GetAtt NextJsRegenerationQueue.Arn
- !Ref AWS::NoValue
- PolicyName: InvokePolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource:
- !If
- HasNextJsRegenerationLambda
- !GetAtt NextJsRegenerationLambda.Arn
- !Ref AWS::NoValue
NextJsDefaultLambda:
Condition: IsNextJs
Type: AWS::Lambda::Function
Expand Down Expand Up @@ -334,6 +369,84 @@ Resources:
FunctionName: !Ref NextJsApiLambda
Qualifier: !GetAtt NextJsApiLambdaCurrentVersion.Version
MaximumRetryAttempts: 1
NextJsRegenerationQueue:
Condition: HasNextJsRegenerationLambda
Type: AWS::SQS::Queue
DeletionPolicy: Delete
Properties:
QueueName: !Sub '${Bucket}.fifo'
FifoQueue: true
NextJsRegenerationLambdaRole:
Condition: HasNextJsRegenerationLambda
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Policies:
- PolicyName: S3Policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:GetObject*
- s3:GetBucket*
- s3:List*
- s3:DeleteObject*
- s3:PutObject*
- s3:Abort*
Resource:
- !Sub 'arn:aws:s3:::${Bucket}'
- !Sub 'arn:aws:s3:::${Bucket}/*'
- PolicyName: SQSPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- sqs:ReceiveMessage
- sqs:ChangeMessageVisibility
- sqs:GetQueueUrl
- sqs:DeleteMessage
- sqs:GetQueueAttributes
Resource: !GetAtt NextJsRegenerationQueue.Arn
NextJsRegenerationLambda:
Condition: HasNextJsRegenerationLambda
Type: AWS::Lambda::Function
Properties:
Code:
S3Bucket: !Ref LambdaBucket
S3Key: !Ref NextJsRegenerationLambdaKey
Role: !GetAtt NextJsRegenerationLambdaRole.Arn
Description: Regeneration Lambda function for Next.js
Handler: index.handler
Runtime: nodejs14.x
Timeout: 30
MemorySize: 512
DependsOn:
- NextJsRegenerationLambdaRole
NextJsRegenerationLambdaCurrentVersion:
Condition: HasNextJsRegenerationLambda
Type: AWS::Lambda::Version
Properties:
FunctionName: !Ref NextJsRegenerationLambda
UpdateReplacePolicy: Delete
DeletionPolicy: Delete
NextJsRegenerationLambdaEventSourceMapping:
Condition: HasNextJsRegenerationLambda
Type: AWS::Lambda::EventSourceMapping
Properties:
Enabled: true
EventSourceArn: !GetAtt NextJsRegenerationQueue.Arn
FunctionName: !GetAtt NextJsRegenerationLambda.Arn
ViewerRequestRole:
Type: 'AWS::IAM::Role'
Properties:
Expand Down

0 comments on commit 2bda454

Please sign in to comment.