์ฌ๋ณด์ธ์ !
์๋ฒ๋ฆฌ์ค์์ razzle ํ๋ก์ ํธ(materialUI, Typescript, Apollo & After)๋ฅผ ์คํํ๋ ค๊ณ ํฉ๋๋ค.
Serverless๋ก ์ด๊ฒ์ ์ฒ๋ฆฌํ๋ ๊ฐ์ฅ ์ข์ ๋ฐฉ๋ฒ์ ๋ฌด์์
๋๊น?
ํน์ ์๊ณ ๊ณ์ ์๊ฐ ์๋ค๋ฉด ๊ธฐ๊บผ์ด ์ดํด๋ณด๊ฒ ์ต๋๋ค. ๊ฐ์ฌ ํด์ !
๋๊ตฐ๊ฐ ๋ฐฉ๊ธ ์ด๊ฒ์ ํ๊ณ ๊ทธ๊ฒ์ ๋ํด ํธ์ํ์ต๋๋ค. ๋ฉ์ง ์๊ฐ ๋ ๊ฒ์ ๋๋ค.
ํธ์์ ์ฐพ์ ์ ์์ต๋๋ค! ๋น์ ์ ๊ทธ๊ฒ์ ๊ฐ์ง๊ณ ์์ต๋๊น?
๋ด ์งน์งน์ด - ๋ฐฉ๊ธ index.js (๋ชจ๋ ํซ๋ก๋๊ฐ ๋ฐ์ํ๋ ๊ณณ)๋ฅผ ๋ค์์ผ๋ก ๋์ฒดํ์ต๋๋ค.
import awsServerlessExpress from 'aws-serverless-express'
import app from './server'
const binaryMimeTypes = [
'application/octet-stream',
'font/eot',
'font/opentype',
'font/otf',
'image/jpeg',
'image/png',
'image/svg+xml',
]
const server = awsServerlessExpress.createServer(app, null, binaryMimeTypes)
export const handler = (event, context, callback) => {
awsServerlessExpress.proxy(server, event, context)
}
๊ทธ๋ฐ ๋ค์ npm run build
๋ฅผ ์คํํ๊ณ node_modules/
๋ฅผ build/
๋ณต์ฌํ๊ณ node_modules/
ํด๋์ ํจ๊ป server.js
์์ถํ์ฌ AWS Lambda์ ์
๋ก๋ํฉ๋๋ค. public/
๋ชจ๋ ํญ๋ชฉ์ S3์ ์
๋ก๋๋๊ณ Cloudfront๋ฅผ ํตํด ์ ์ก๋ฉ๋๋ค.
Lambda -> API ๊ฒ์ดํธ์จ์ด ์ธํ๋ผ์ ๊ฒฝ์ฐ Terraform์ ์ฌ์ฉํ๊ณ ์ด ๊ฐ์ด๋๋ฅผ ๋ฐ๋์ต๋๋ค. https://www.terraform.io/docs/providers/aws/guides/serverless-with-aws-lambda-and-api-gateway.html
๊ทธ๋๋ ์๋ฒฝํ์ง๋ ์์ต๋๋ค. ํด๋ผ์ด์ธํธ ์ธก ๋ฒ๋ค(https://static.jobsok.io/)์์ ๋ก๋ ์ ์ด ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.
์
๋ฐ์ดํธ:
๋๋ ๋น์ ์ ์ ์ฅํ์ง ์์ต๋๋ค. node_modules๋ฅผ ์ญ์ ํ๊ณ ๋ค์ ์ค์นํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
File sizes after gzip:
210.38 KB (+8 B) build/static/js/bundle.1a960add.js
1.96 KB build/static/css/bundle.5865fae5.css
์ฝ๋ ๋ณ๊ฒฝ ์์ด - ํด๋ผ์ด์ธํธ ์ธก ๋ฒ๋ค์ด ๋ค์ ์๋ํ๋๋ก ๋ง๋ค์์ต๋๋ค.
๋ด handler.js(๋๋ index.js)๋ ๋์ผํฉ๋๋ค.
๊ทธ๋ฐ ๋ค์ webpack(project + node_modules)์ผ๋ก ๋ชจ๋ ๊ฒ์ ๋น๋ํฉ๋๋ค. (์ด๋ฅผ ์ํด razzle.config.js๋ฅผ ์์ ํ๋ฉด ๋ฉ๋๋ค.)
์ ๋ Terraform์ด ์๋ ์๋ฒ๋ฆฌ์ค๋ก ์์ ํ๊ณ ์์ต๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก ๋์ผํฉ๋๋ค. ํธ๋ค๋ฌ์ ๋ํ ์๋ ํจํค์ง๋ฅผ ๊ฐ์ ธ์ค๊ธฐ๋ง ํ๋ฉด ๋ฉ๋๋ค(์ด๋ค ์ด์ ๋ก webpack์ ์ด ํจํค์ง๋ฅผ ํฌ์ฅํ์ง ์์ต๋๋ค. ์๋ง๋ ๋ค๋ฅธ ํ์ผ/๊ฒฝ๋ก์ ์๊ธฐ ๋๋ฌธ์ ๋์ค์ ์์ ๋ ๊ฒ์ ๋๋ค).
์๋ฒ๋ฆฌ์ค๋ก ์ด ๋ชจ๋ ๊ฒ์ ์ ๋ก๋ํ๋ฉด ๋ฉ๋๋ค. ํ๋ก์ ํธ๋ https://github.com/CuistotduCoin/cuistot ์์ ๋ณผ ์ ์์ต๋๋ค.
์๋ฒฝํจ๊ณผ๋ ๊ฑฐ๋ฆฌ๊ฐ ๋ฉ๋ค.
๊ทธ๊ฑด ๊ทธ๋ ๊ณ , @jaredpalmer , razzle/webpack ์ค์ ์ ๋ํด ์ ๋ฅผ ๋์์ฃผ์๊ฒ ์ต๋๊น?
์ต๊ณ ์ ์ฑ๋ฅ์ผ๋ก ๋ฒ๋ค(node_modules ํฌํจ)์ ํ์ํ ๋ชจ๋ ๊ฒ์ ํฌํจํ๊ณ ์ถ์ต๋๋ค. ์/ํ์ด ์์ต๋๊น? (Typescript ๊ตฌ์ฑ ์ฌ์ฉ)
@rozenmd razzle.config ๋ฅผ ๊ณต์ ํ ์ ์์ต๋๊น?
@romainquellec ๊ด์ฐ์ด ๋น์ด ์์ต๋๋ค -
๊ธ์์, ์ด์ ๋ ๋ชจ๋ฅด๊ฒ ์ง๋ง ์ข
์์ฑ์ ์ ์๋ aws-serverless-express์ ์ก์ธ์คํ ์ ์์ต๋๋ค(๋ ๋ง์ ์๋ ์์).
๊ทธ๋ฆฌ๊ณ ์ด ๋ชจ๋ ๊ฒ์ ์ต์ ํํ๊ธฐ ์ํด ์ต์ข
๋น๋์์ aws-sdk๋ฅผ ์ ์ธํ๊ณ ์ถ์ต๋๋ค(ํญ์ ๋๋ค ํจ์์์ ์ฌ์ฉํ ์ ์์ง๋ง ๋์ค์ ๋ณผ ์ ์์)
๋๋ ๊ทธ๊ฒ์ด ์ธ๋ถ์์ ์ค๋ ๊ฒ์ด๋ผ๊ณ ์๊ฐํ์ง๋ง ๋๋ ๊ทธ๊ฒ์ ์์๋ผ ์ ์์ต๋๋ค.
์, ๋๋ ๊ฐ์ ๋ฌธ์ ๊ฐ์์์ต๋๋ค. ๋ด ์ ์ผํ ํด๊ฒฐ์ฑ ์ ๋ชจ๋ node_modules๋ก server.js๋ฅผ ์์ถํ๋ ๊ฒ์ด ์์ต๋๋ค.
๋๋ ๋๋ค์์ ํ ์ ์์ต๋๋ค. ๊ณต์ ์ ํ ํฌ๊ธฐ๋ 50mo์
๋๋ค.
๋ชจ๋๋ฅผ ์ํ ์ผ๋ฐ์ ์ธ ์ง๋ฌธ: node_modules๋ฅผ razzle๊ณผ ํจ๊ป ์ข์ ์ฑ๋ฅ์ผ๋ก ๋ฌถ๋ ๋ฐฉ๋ฒ. ๊ฐ์ฌ ํด์ !
์ฐ๋ฆฌ๋ ๋๋ค๋ฅผ ์ฌ์ฉํ์ง ์์ง๋ง razzle.config.js์์ ๊ธฐ๋ณธ ์ธ๋ถ ์ต์ ์ ์ ๊ฑฐํ์ฌ ๋ชจ๋ node_modules๋ฅผ ๋ฒ๋ค๋ก ์ ๊ณตํฉ๋๋ค. ์ด๊ฒ์ ๋น๋ ํด๋๊ฐ ์ฌ์ค์ ๋น๋ ์ํฐํฉํธ์์ ์๋ฏธํ๊ธฐ ๋๋ฌธ์ ๋ฉ์ง๋ค. ๊ทธ๋ฐ ๋ค์ s3์ ๋ฃ์ ๋ค์ Jenkins ์์ ์ด ์ ํด๋๋ฅผ ๊ฐ ์๋ฒ๋ก ๊ฐ์ ธ์ค๊ณ pm2๋ฅผ ๋ค์ ์์ํฉ๋๋ค.
@jaredpalmer - razzle.config.js
๋ก ์์ ์ ๊ฒ์ํ ์ ์๋ ๊ธฐํ๊ฐ ์์ต๋๊น?
razzle.config.js์์ ๊ธฐ๋ณธ ์ธ๋ถ ์ต์ ์ ์ ๊ฑฐํ์ฌ:
๊ทธ๋ ๊ฒ?
modify(config, { target, dev }) {
if (target === 'node' && !dev) {
config.externals = [];
}
return config;
}
@romainquelec ์๋ํฉ๋๋ค!!
module.exports = {
modify: (config, { target, dev }, webpack) => {
// do something to config
const appConfig = config // stay immutable here
if (target === 'node' && !dev) {
appConfig.externals = []
}
return appConfig
},
}
์๋ ์ค์
๋๋ค. ์ข์ ! ๐ ๐
์๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค! ์ด๋ฒ ์ฃผ๋ง์ ์ดํด๋ณด๊ฒ ์ต๋๋ค. @rozenmd์ ์ฐธ์ฌ
๋ถํํ๋ ์ด๋ฒ ์ฃผ๋ง @romainquellec์ ์์ ์๊ฐ์ด ์์ง๋ง PR์ ๋ํด ๊ฒํ /ํ๋ ฅํ๊ฒ ๋์ด ๊ธฐ์ฉ๋๋ค.
๋๋ ์ด๊ฒ์ ๋ซ๋๋ค. ๊ฐ๋ฅํ ํ ๋นจ๋ฆฌ PR์ ์ ์ถํ ๊ฒ์ ๋๋ค.
@romainquellec ๊ทธ ํ ๋ฆฌํ์คํธ๋ฅผ ํด๋ณธ ์ ์ด ์์ต๋๊น? ์ฌ์ ํ ์๋ฒ๋ฆฌ์ค ํ๊ฒฝ์์ Razzle ํ๋ก์ ํธ๋ฅผ ์คํํ๊ณ ์์ต๋๊น? ๋๋์ด ๊ฐ๋ฅ์ฑ์ ํ๊ตฌํ๊ธฐ ์์ํ๊ณ ์ด ์ค๋ ๋๋ฅผ ์ฐ์ฐํ ๋ฐ๊ฒฌํ์ต๋๋ค. :)
@mbrochh ๊ฒฐ๊ตญ ํ ๋ฆฌํ์คํธ๋ฅผ ๋ณด์ง ๋ชปํ์ง๋ง ์ ๋ ์ฌ์ ํ ๋ชจ๋ ์๋ฒ๋ฆฌ์ค ์๋ฒ์ฌ์ด๋ ๋ ๋๋ง์ ์ํด https://onlineornot.com ์์ Razzle์ ์ฌ์ฉํ๊ณ
๋๋ ๋ํ ์ด ๋ธ๋ก๊ทธ ๊ฒ์๋ฌผ์ ๋ฐ๊ฒฌํ์ต๋๋ค: https://medium.com/@RozenMD/build -your-own-app-with-react-graphql-and-serverless-architecture-part-1-server-side-rendering-f0d0144ff5f
๋๋ ๋จธ๋ฆฌ ์ฐ์ ์ผ๋ก ๋ค์ด๋นํ๊ณ ๊ทธ ๊ฒ์๋ฌผ์ ๋ฐ๋ผ๊ฐ๋ ค๊ณ ๋ ธ๋ ฅํด์ผ ํ ๊ฒ ๊ฐ์์ :)
๊ฒ์๋ฌผ์ ๊ฐ์ฌ๋๋ฆฝ๋๋ค!
https://maxrozen.com/2018/08/08/start-your-own-app-with-react-part-1 ์ด ์ฝ๋ ํ์ ์ง์ ์ ๋ํ ๋ ๋์ ๋งํฌ์ผ ๊ฒ์ ๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ณ ๋ง์! :์๋ค:
Razzle์ AWS ๋๋ค์ ๋ฐฐํฌํ ์ฌ๋์ด ์์ต๋๊น?
๋ํ AWS ๋๋ค์์ Razzle์ ๋ํ ์๋ฅผ ์ฐพ๊ณ ์์ต๋๋ค. ๋๊ตฐ๊ฐ๊ฐ ๊ทธ๊ฒ์ํ๊ณ ์ฐ๋ฆฌ์ ๊ณต์ ํ๊ฒ๋๋ค๋ฉด ์ ๋ง ๊ฐ์ฌํ๊ฒ ์ต๋๋ค.
์ด๊ฒ์ด ๋ด๊ฐ AWS CDK๋ฅผ ์ฌ์ฉ ํ์ฌ AWS Lambda์ Razzle ์ฑ์ ๋ฐฐํฌํ ๋ฐฉ๋ฒ์ ๋๋ค.
import * 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 RazzleStack 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',
'RAZZLE_GRAPHQL_URL',
'RAZZLE_MAPBOX_ACCESS_TOKEN',
'RAZZLE_GOOGLE_RECAPTCHA_SITE',
'RAZZLE_GA_ID',
];
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();
}
/**
* 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,
});
}
}
@jgcmarins์ razzletack ์ค์ ์ ๋ํด ์ค๋ช ํฉ๋๋ค. ์ด๊ฒ์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋จ๊ณ๋ณ๋ก ์ํํ ์ ์์ต๋๊น? razzle ๋ฌธ์์ ์ถ๊ฐํ๊ณ ์ถ์ต๋๋ค.
https://razzle-git-canary.jared.vercel.app/deployment-options/aws
๋์์ด ํ์ํฉ๋๋ค. ๋๊ฐ ๋์์ค ์ ์์ต๋๊น?
@fivethreeo ๋ ๋จ๊ณ๋ณ๋ก AWS ๊ณ์ ์์ CDK๋ฅผ ์ค์ ํ๊ณ ๋ช ๊ฐ์ง ์ถ๊ฐ ํ๋๊ทธ๋ก cdk deploy
๋ฅผ ์คํํ๋ ๊ฒ๋ณด๋ค ๋ผ์ด๋ธ \o/
https://razzle-git-canary.jared.vercel.app/deployment-options/aws ์์ ๋๋ฝ๋ ๊ฒ์ด ์์ต๋๊น?
?
๊ฒฝ๋ก๊ฐ ์๋ ๋๋ฉ์ธ์ผ๋ก ๋ฌด๋๋ฅผ ๋ง๋ค๊ณ ์ถ์ต๋๋ค. rds, ํค ํ์ , TypeOrm, typegraphql, Formik ๋ฐ jwt๋ฅผ ์ฌ์ฉํ์ฌ ์์ ํ ๊ธฐ๋ฅ์ ์๋ฅผ ๋ง๋ค ์ ์์ง๋ง razzle ์์ฒด์ ์๋ ์๋๋๋ค.
๋ฌ๋ฆฌ ๋น ์ง ๊ฒ์ ์์ต๋๋ค.
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
์ด๊ฒ์ด ๋ด๊ฐ AWS CDK๋ฅผ ์ฌ์ฉ ํ์ฌ AWS Lambda์ Razzle ์ฑ์ ๋ฐฐํฌํ ๋ฐฉ๋ฒ์ ๋๋ค.
RazzleStack