Deploy Razzle on AWS
Add razzle config.
// razzle.config.js'use strict';const path = require('path');module.exports = { options: { buildType: 'serverless' }, modifyPaths({ paths, }) { paths.prodAppServerIndexJs = path.join(paths.appSrc, 'index.prod'); return paths; }, modifyWebpackConfig({ env: { target, dev, }, webpackConfig, paths, }) { if (target === 'node') { if (!dev) { webpackConfig.entry.server = [paths.prodAppServerIndexJs]; } } return webpackConfig; },};
Modify src/server.js
like so:
const public_bucket_domain = process.env.PUBLIC_BUCKET_DOMAIN || '';const public_bucket_url = public_bucket_domain ? `https://${public_bucket_domain}` : '';const cssLinksFromAssets = (assets, entrypoint) => { return assets[entrypoint] ? assets[entrypoint].css ? assets[entrypoint].css.map(asset=> `<link rel="stylesheet" href="${public_bucket_url}${asset}">` ).join('') : '' : '';};const jsScriptTagsFromAssets = (assets, entrypoint, extra = '') => { return assets[entrypoint] ? assets[entrypoint].js ? assets[entrypoint].js.map(asset=> `<script src="${public_bucket_url}${asset}"${extra}></script>` ).join('') : '' : '';};
Add this to src/index.prod.js
const serverless = require('serverless-http');module.exports = { handler: serverless(require('./server').default)};
Add serverless-http dependency
yarn add serverless-http
Build the Razzle project
yarn build
Init cdk app
mkdir razzle-cdkcd razzle-cdkcdk init app --language typescript
Add razzle-cdk
dependencies in razzle-cdk
directory
yarn add @aws-cdk/aws-s3 @aws-cdk/aws-s3-deployment @aws-cdk/aws-lambda @aws-cdk/aws-apigateway @aws-cdk/aws-ssm @aws-cdk/aws-secretsmanager --dev
Add razzle-cdk/bin/razzle-cdk.ts
// bin/cdk.ts#!/usr/bin/env nodeimport 'source-map-support/register';import * as cdk from '@aws-cdk/core';import { RazzleCdkStack } from '../lib/razzle-cdk-stack';const app = new cdk.App();new RazzleCdkStack(app, 'RazzleCdkStack', {name:'basic'});
Add razzle-cdk/lib/helpers.ts
// razzle-cdk/lib/helpers.tsimport * as SSM from '@aws-cdk/aws-ssm';import * as CDK from '@aws-cdk/core';export const getParam = (scope: CDK.Construct, name: string) => { return SSM.StringParameter.valueForStringParameter(scope, name);};export interface ConfigProps extends CDK.StackProps { name: string;}export class ModeStack extends CDK.Stack { public readonly mode: string = this.node.tryGetContext('mode') || 'development'; public readonly Mode: string = this.mode.replace(/^\w/, (c: string) => c.toUpperCase()); constructor(scope: CDK.Construct, id: string, props?: ConfigProps) { super(scope, id, props); }}
Add razzle-cdk/lib/razzle-cdk-stack.ts
// razzle-cdk/lib/razzle-cdk-stack.tsimport * as CDK from '@aws-cdk/core';import * as S3 from '@aws-cdk/aws-s3';import * as S3Deployment from '@aws-cdk/aws-s3-deployment';import * as Lambda from '@aws-cdk/aws-lambda';import * as APIGateway from '@aws-cdk/aws-apigateway';import * as SSM from '@aws-cdk/aws-ssm';import * as SecretsManager from '@aws-cdk/aws-secretsmanager';import { ConfigProps, getParam, ModeStack } from './helpers';export class RazzleCdkStack extends ModeStack { constructor(app: CDK.App, id: string, props: ConfigProps) { super(app, id, props); /** * S3 bucket to the /public folder */ const publicBucketName = `my-razzle-app-bucket-public-files-${this.mode}`; const bucketPublicFiles = new S3.Bucket(this, publicBucketName, { publicReadAccess: true, bucketName: publicBucketName.toLowerCase(), }); /** * Store S3 bucket name */ new SSM.StringParameter(this, `MyRazzleAppBucketAssetsName${this.Mode}`, { description: `My Razzle App S3 Bucket Name for Assets on ${this.Mode}`, parameterName: `/${props.name}/S3/Assets/Name`, stringValue: bucketPublicFiles.bucketName, }); /** * Store S3 domainName name */ new SSM.StringParameter(this, `MyRazzleAppBucketAssetsDomainName${this.Mode}`, { description: `My Razzle App S3 Bucket DomainName for Assets on ${this.Mode}`, parameterName: `/${props.name}/S3/Assets/DomainName`, stringValue: bucketPublicFiles.bucketDomainName, }); /** * Deploy public folder of build to `my-razzle-app-bucket-public-files-${this.mode}` bucket */ new S3Deployment.BucketDeployment(this, `${publicBucketName}-deploy`, { sources: [S3Deployment.Source.asset('../build/public')], destinationBucket: bucketPublicFiles, }); /** * Environment Variables for SSR Function */ const environmentKeys = [ 'NODE_ENV', ]; const environmentSecret = SecretsManager.Secret.fromSecretAttributes( this, `MyRazzleAppEnvironmentSecret${this.Mode}`, { secretArn: getParam(this, `MyRazzleAppSecretsArn${this.Mode}`), }, ); let environment: { [key: string]: string } = {}; for (const key of environmentKeys) { environment[key] = environmentSecret.secretValueFromJson(key).toString(); } environment['PUBLIC_BUCKET_DOMAIN'] = bucketPublicFiles.bucketRegionalDomainName; /** * Razzle SSR Function */ const myRazzleAppSsrFunction = new Lambda.Function(this, `MyRazzleAppSSRFunction${this.Mode}`, { description: `Lambda Function that runs My Razzle App SSR on ${this.Mode}`, code: Lambda.Code.fromAsset('../build', { exclude: ['public', 'static', '*.json'], }), handler: 'server.handler', runtime: Lambda.Runtime.NODEJS_12_X, memorySize: 512, timeout: CDK.Duration.seconds(5), environment, tracing: Lambda.Tracing.ACTIVE, }); /** * Razzle ApiGateway */ const razzleSsrApiGatewayName = `MyRazzleAppSSRApiGateway${this.Mode}`; const api = new APIGateway.RestApi(this, razzleSsrApiGatewayName, { description: `ApiGateway that exposes My Razzle App SSR on ${this.Mode}`, binaryMediaTypes: ['*/*'], endpointTypes: [APIGateway.EndpointType.REGIONAL], deployOptions: { stageName: this.mode, }, }); const integration = new APIGateway.LambdaIntegration(myRazzleAppSsrFunction); const root = api.root; const pathApi = api.root.addResource('{proxy+}'); root.addMethod('GET', integration); pathApi.addMethod('ANY', integration); /** * Razzle ApiGateway ID */ new SSM.StringParameter(this, `MyRazzleAppAPIGatewayRestId${this.Mode}`, { description: `My Razzle App ApiGateway ID on ${this.Mode}`, parameterName: `/${props.name}/APIGateway/ApiId`, stringValue: api.restApiId, }); }}
Bootstrap and deploy
aws configureecho '{"NODE_ENV": "development" }' | jq '.' > mycreds.jsonaws secretsmanager create-secret --name MyRazzleAppEnvironmentSecretDevelopment \ --description "My Development secrets created with the CLI" \ --secret-string file://mycreds.json > arn.jsonarn=($(cat arn.json | jq --raw-output '.ARN'))echo $arnaws ssm put-parameter --name=MyRazzleAppSecretsArnDevelopment --value=$arnyarn cdk bootstrapyarn cdk deploy