Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
JavaScript(TypeScript)で メディアサイトを インフラから構築する方法 /...
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Hidetaka Okamoto
November 27, 2021
Programming
4.4k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
JavaScript(TypeScript)で メディアサイトを インフラから構築する方法 / jsconf-jp-2021
Hidetaka Okamoto
November 27, 2021
More Decks by Hidetaka Okamoto
See All by Hidetaka Okamoto
OpenAI APIで API Changelogを要約してみた話 / chatgpt-osaka-1
hideokamoto
0
690
コミュニティ運営から 中の人に変わって感じたこと
hideokamoto
0
140
Developerが Developer Advocateになった話 / dev-rel-meetup-tokyo-71
hideokamoto
0
380
Jamstack開発者のための App Runner入門
hideokamoto
1
540
WordPressでの webサイト制作2022 / ngk2022s
hideokamoto
0
490
AWS上でStripeを利用したアプリをより安全にデプロイする方法 /jaws-pankration-2021
hideokamoto
1
240
Shifter Headlessと Headless WordPressの紹介
hideokamoto
0
2.1k
Stripe & Next.js + AWS Amplify で会員 + 定期課金機能 / JP_Stripes20210903
hideokamoto
7
3.3k
後付けで 従量課金プランの 提供を開始した話 / 20210609-jp_stripes
hideokamoto
0
250
Other Decks in Programming
See All in Programming
tsserverとは何だったのか、これからどうなるのか
nowaki28
1
460
Vite+ Unified Toolchain for the Web
naokihaba
0
210
ECSアプリログをFireLensでコスト削減しようとしたけど諦めた話 in Fargate×Node.js
akihisaikeda
2
3.9k
生成AI時代にこそ効くGo | Why Go Works in the Age of Generative AI
mom0tomo
8
3.2k
PHPで使える日時の表現と、その知り方 #frontend_phpcon_do
o0h
PRO
0
230
ユニットテストの先へ:テスト技法で要求・仕様を整理するJava開発実践 / Beyond_Unit_Testing_Practical_Java_Development_Techniques_for_Organizing_Requirements_and_Specifications
shimashima35
0
380
ふつうのFeature Flag実践入門
irof
7
3.7k
Spec-Driven Development with AI-Agents: From High-Level Requirements to Working Software
antonarhipov
2
480
These Five Tricks Can Make Your Apps Greener, Cheaper, & Nicer
hollycummins
0
280
Modding RubyKaigi for Myself
yui_knk
0
910
3Dシーンの圧縮
fadis
1
680
代数的データ型って何が嬉しいの? #frontend_phpcon_do
kajitack
8
3.3k
Featured
See All Featured
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
201
75k
WCS-LA-2024
lcolladotor
0
620
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
2.9k
Un-Boring Meetings
codingconduct
0
310
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
600
Balancing Empowerment & Direction
lara
6
1.1k
Avoiding the “Bad Training, Faster” Trap in the Age of AI
tmiket
0
170
Designing for Timeless Needs
cassininazir
1
250
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
37
6.5k
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
280
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.5k
The Cult of Friendly URLs
andyhume
79
6.9k
Transcript
JavaScript(TypeScript)Ͱ ϝσΟΞαΠτΛ Πϯϑϥ͔Βߏங͢Δํ๏ JSConf JP 2021 2021/11/27 Hidetaka Okamoto
Agenda • WebαΠτߏஙʹ͓͚ΔΠϯϑϥͷଟ༷Խ • IaaS / FaaSͱIaCΛར༻ͨ͠JSΦϯϦʔͷΠϯϑϥఆٛ • AWSʹ͓͚ΔJSͷΈͰͷߏஙύλʔϯ3छྨ •
AWSͰfaust.js(Next.js)ͰWordPressΛར༻͢Δ߹ͷҙ
About me • Ԭຊलߴ(Okamoto Hidetaka) • JS(TS) developer • React
/ NestJS / AWS CDK • AWS / Stripe / Alexa / Shifter • དྷि͔Β࠶ͼձࣾһ
Talk about… • My Blog ( https://wp-kyoto.net ) • Backend:
WordPress • Frontend: Next.js (with Ionic) • Infrastructure: AWS (AWS CDK) • Misc: Stripe / AWS Amplify / Algolia
Agenda • WebαΠτߏஙʹ͓͚ΔΠϯϑϥͷଟ༷Խ • IaaS / FaaSͱIaCΛར༻ͨ͠JSΦϯϦʔͷΠϯϑϥఆٛ • AWSʹ͓͚ΔJSͷΈͰͷߏஙύλʔϯ3छྨ •
AWSͰfaust.js(Next.js)ͰWordPressΛར༻͢Δ߹ͷҙ
WebαΠτߏஙͰͷΠϯϑϥͷଟ༷Խ(Ұྫ) • αʔόʔωοτϫʔΫΛཧ͔Β༻ҙɾηοτΞοϓ • αʔόʔΛआΓͯɺͦͷதʹLAMP / MEAN stackͳͲΛߏ (VPS) •
ίϯςφΛར༻ͨ͠ɺImmutableͳڥΛڞ༗ɾσϓϩΠ (GAE/EKS) • ϑϧϚωʔδυͳSaaSΛͬͯɺΞϓϦͷΈσϓϩΠ (Netlify / Vercel) • ΞϓϦϚωʔδυܕͷSaaSͰɺαΠτӡӦʹूத (WP.com/STUDIO)
Πϯϑϥͷίʔυཧ “Infrastructure as Code (IaC)” • Chef / Ansible /
terraform / Serverless Framework / Docker / etc… • ʮ͋Δ͖ߏʯΛίʔυͰఆٛ͠ɺVCSͰཧ͢Δ • ʮͦͷίʔυ͕͋Εɺ୭Ͱಉ͡ڥ͕࡞ΕΔʯ • ʮԶͷڥͰಈ͍͚ͨͲʁʯͷղফ • αʔόʔ૿ڧೖΕସ͑࣌ͷޮԽͱ࡞ۀϛεͷ༧
IaCͷఆ͕ٛ JavaScriptͰॻ͚Εɺ Ͱ͖Δ͜ͱ͕૿͑Δɾɾɾʁ
Agenda • WebαΠτߏஙʹ͓͚ΔΠϯϑϥͷଟ༷Խ • IaaS / FaaSͱIaCΛར༻ͨ͠JSΦϯϦʔͷΠϯϑϥఆٛ • AWSʹ͓͚ΔJSͷΈͰͷߏஙύλʔϯ3छྨ •
AWSͰfaust.js(Next.js)ͰWordPressΛར༻͢Δ߹ͷҙ
JSͰΠϯϑϥఆ͕ٛՄೳͳπʔϧ(Ұ෦) • Serverless Framework (AWSͷΈ) • AWS CDK • Adapt
Serverless Framework (JS Objectܕ) import type { AWS } from
'@serverless/typescript'; const serverlessConfiguration: AWS = { service: 'aws-nodejs-typescript', frameworkVersion: '*', provider: { name: 'aws', runtime: 'nodejs12.x', }, functions: { hello: { handler: 'handler.hello', events: [ { http: { method: 'get', path: 'hello', } } ] } } } • AWSͷΈରԠ(2021/11) • ܕใ: @types/serverless • AWS Lambda + API GW
AWS CDK (Classܕ) import * as core from "@aws-cdk/core"; import
* as apigateway from "@aws-cdk/aws-apigateway"; import * as lambda from "@aws-cdk/aws-lambda"; export class WidgetService extends core.Construct { constructor(scope: core.Construct, id: string) { super(scope, id); const handler = new lambda.Function(this, "WidgetHandler", { runtime: lambda.Runtime.NODEJS_14_X, // So we can use async in widget.js code: lambda.Code.fromAsset("resources"), handler: "main", }); bucket.grantReadWrite(handler); // was: handler.role); const api = new apigateway.RestApi(this, "widgets-api", { restApiName: "Widget Service", description: "This service serves widgets." }); const getWidgetsIntegration = new apigateway.LambdaIntegration(handler, { requestTemplates: { "application/json": '{ "statusCode": "200" }' } }); api.root.addMethod("GET", getWidgetsIntegration); // GET / } } • CloudFormationͷϥούʔ • AWSϦιʔεఆٛΛ TypeScriptͰॻ͚Δ • CloudFormation YAMLͷ มͰग़Մೳ
Adapt (JSXܕ) import Adapt, { Group, handle } from "@adpt/core";
import { UrlRouter } from "@adpt/cloud/http"; import { NodeService, ReactApp } from "@adpt/cloud/nodejs"; import { Postgres } from "@adpt/cloud/postgres"; import { k8sProdStyle, k8sTestStyle, laptopStyle } from "./ styles"; function App() { const pg = handle(); const app = handle(); const api = handle(); return <Group> <UrlRouter port={8080} routes={[ { path: "/api/", endpoint: api }, { path: "/", endpoint: app } ]} /> <ReactApp handle={app} srcDir="../frontend" /> <NodeService handle={api} srcDir="../backend" connectTo={pg} /> <Postgres handle={pg} /> </Group>; } • React JSXͰߏఆٛ • AWSҎ֎Ͱ͑Δ • v0.xͷͨΊɺ prodಋೖ৻ॏʹ
JSΛͬͨΠϯϑϥఆٛ • Class / Object / JSXͳͲɺ༷ʑͳه๏͕Մೳ • TypeScriptͷܕఆٛΛ׆༻࣮͕ͯ͠جຊܗ •
AWS / GCPͳͲͷαʔϏεಠࣗͷઃఆఆٛʹTSͷྗ͕͑Δ • ʢ؍ଌൣғͰʣެࣜͰπʔϧΛϦϦʔε͍ͯ͠ΔAWS͕ڧΊ
Agenda • WebαΠτߏஙʹ͓͚ΔΠϯϑϥͷଟ༷Խ • IaaS / FaaSͱIaCΛར༻ͨ͠JSΦϯϦʔͷΠϯϑϥఆٛ • AWSʹ͓͚ΔJSͷΈͰͷߏஙύλʔϯ3छྨ •
AWSͰfaust.js(Next.js)ͰWordPressΛར༻͢Δ߹ͷҙ
AWS CDK import * as core from "@aws-cdk/core"; import *
as apigateway from "@aws-cdk/aws-apigateway"; import * as lambda from "@aws-cdk/aws-lambda"; export class WidgetService extends core.Construct { constructor(scope: core.Construct, id: string) { super(scope, id); const handler = new lambda.Function(this, "WidgetHandler", { runtime: lambda.Runtime.NODEJS_14_X, // So we can use async in widget.js code: lambda.Code.fromAsset("resources"), handler: "main", }); bucket.grantReadWrite(handler); // was: handler.role); const api = new apigateway.RestApi(this, "widgets-api", { restApiName: "Widget Service", description: "This service serves widgets." }); const getWidgetsIntegration = new apigateway.LambdaIntegration(handler, { requestTemplates: { "application/json": '{ "statusCode": "200" }' } }); api.root.addMethod("GET", getWidgetsIntegration); // GET / } }
AWS CDKΛ༻͍ͨߏஙํ๏ • CloudFormationΛTypeScriptͰهड़͢Δ • Construct LibraryΛར༻͢Δ • AWS AmplifyΛOverwrite͢ΔʢNEW
!ʣ
CloudFormationΛ TypeScriptͰ هड़͢Δ const bucket = new s3.CfnBucket(this, "MyBucket", {
bucketName: "MyBucket", corsConfiguration: { corsRules: [{ allowedOrigins: ["*"], allowedMethods: ["GET"] }] } }); const cfnDistribution = new cloudfront.CfnDistribution(this, 'MyCfnDistribution', { distributionConfig: { enabled: false, origins: [{ domainName: 'domainName', id: 'id', // the properties below are optional connectionAttempts: 123, connectionTimeout: 123, originCustomHeaders: [{ headerName: 'headerName', headerValue: 'headerValue', }], originPath: 'originPath', originShield: { enabled: false, originShieldRegion: 'originShieldRegion', }, s3OriginConfig: { originAccessIdentity: 'originAccessIdentity', }, }], … • AWSϦιʔεΛ 1ͭ1ͭఆٛ • 👍 ৄࡉͳઃఆ͕Մೳ • 😖 AWSͷ͕ࣝଟ͘ඞཁ
AWS CDKͷ Construct Library Λར༻͢Δ import * as core from
"@aws-cdk/core"; import * as apigateway from "@aws-cdk/aws-apigateway"; import * as lambda from "@aws-cdk/aws-lambda"; import * as s3 from "@aws-cdk/aws-s3"; export class WidgetService extends core.Construct { constructor(scope: core.Construct, id: string) { super(scope, id); const bucket = new s3.Bucket(this, "WidgetStore"); const handler = new lambda.Function(this, "WidgetHandler", { runtime: lambda.Runtime.NODEJS_14_X, // So we can use async in widget.js code: lambda.Code.fromAsset("resources"), handler: "widgets.main", environment: { BUCKET: bucket.bucketName } }); bucket.grantReadWrite(handler); // was: handler.role); const api = new apigateway.RestApi(this, "widgets-api", { restApiName: "Widget Service", description: "This service serves widgets." }); const getWidgetsIntegration = new apigateway.LambdaIntegration(handler, { requestTemplates: { "application/json": '{ "statusCode": "200" }' } }); api.root.addMethod("GET", getWidgetsIntegration); // GET / } } • CFNϦιʔεΛ நԽͨ͠ϥΠϒϥϦ܈ • 👍 ͋ΔఔఆٛࡁΈͷ ɹߏ͕͑Δ • 😖 ઃఆ͕not for meͷ߹ ɹ݁ہίʔυΛॻ͘͜ͱʹ
@aws-cdk/aws- lambda-nodejs ͕ศར new lambda.NodejsFunction(this, 'my-handler', { bundling: { commandHooks:
{ beforeBundling(inputDir: string, outputDir: string): string[] { return [ `echo hello > ${inputDir}/a.txt`, ]; }, afterBundling(inputDir: string, outputDir: string): string[] { return [`cp ${inputDir}/b.txt ${outputDir}/txt`]; }, beforeInstall() { return []; }, }, minify: true, // minify code, defaults to false sourceMap: true, // include source map, defaults to false target: 'es2020', loader: { // Use the 'dataurl' loader for '.png' files '.png': 'dataurl', }, define: { // Replace strings during build time 'process.env.API_KEY': JSON.stringify('xxx-xxxx-xxx'), }, logLevel: lambda.LogLevel.SILENT, // defaults to LogLevel.WARNING keepNames: true, // defaults to false tsconfig: 'custom-tsconfig.json', // use custom-tsconfig.json instead of default, … • TypeScriptͷίʔυΛ esbuildͰίϯύΠϧͯ͠ AWS LambdaʹσϓϩΠ • Lambdaͷ࣮ͱߏ ྆ํ·ͱΊͯTSͰཧͰ͖Δ
Next.jsΛ͏߹ Serverless Next.js https://github.com/serverless-nextjs/serverless-next.js
serverless-nextjs for AWS CDK // Πϯϑϥఆٛ import { NextJSLambdaEdge }
from "@sls-next/cdk-construct"; const app = new NextJSLambdaEdge(this, "NextJsApp", { serverlessBuildOutDir: "./build", domain: { domainNames: ["wp-kyoto.net"], certificate: Certificate.fromCertificateArn( this, “WPKYOTOACM", "arn:aws:acm:us-east-1:xxxxxx" ), }, }); ==================================== // Next.jsͷϏϧυઃఆ import * as cdk from "@aws-cdk/core"; import { Builder } from "@sls-next/lambda-at-edge"; const builder = new Builder(".", "./build", { args: ["build"] }); builder .build().then(() => { const app = new cdk.App(); new MyStack(app, `MyStack`); }) • NextJSLambdaEdgeͰ Next.jsΛCDNΤοδʹσϓϩΠ • CDKͳͷͰΧελϚΠζ༰қ • Next.jsͷ࣮ʹखΛग़͞ͳ͍ͷ Ͱɺ؆୯ʹࣙΊΕΔ
CDKͰͷ ΧελϚΠζྫ const app = new NextJSLambdaEdge(…); // Ωϟογϡઃఆ app.nextLambdaCachePolicy
= new CachePolicy(this, "NextLambdaCache", { queryStringBehavior: CacheQueryStringBehavior.all(), headerBehavior: CacheHeaderBehavior.allowList( "Authorization", “Origin" ), cookieBehavior: { behavior: "all", }, defaultTtl: Duration.seconds(0), maxTtl: Duration.days(365), minTtl: Duration.seconds(0), enableAcceptEncodingBrotli: true, enableAcceptEncodingGzip: true, }); // AWSϦιʔεͷΞΫηεݖ const targets = [app.defaultNextLambda, app.defaultNextLambda]; targets.forEach((target) => { target.addToRolePolicy( new PolicyStatement({ resources: ["*"], actions: ["ssm:GetParameter"], }) ); }); • ΫϥεͷpropsΛ ্ॻ͖ͯ͠ߋ৽ • CDNઃఆΛҰ෦্ॻ͖ • AWSͷΞΫηεݖݶΛ ֦ு
σΟϨΫτϦߏ !"" README.md !"" next-env.d.ts !"" next.config.js !"" tsconfig.json !""
package.json !"" pages !"" public !"" styles !"" components !"" hooks !"" tests # %"" cdk !"" tsconfig.cdk.json !"" cdk # !"" bin # %"" serverless-nextjs-cdk-example-stack.ts !"" cdk.json !"" jest.cdk.config.js %"" yarn.lock • Next.jsجຊߏʹ cdkσΟϨΫτϦΛՃ • CDKελοΫͷTS ผ్tsconfigΛ࡞
GitHubʹࢼͤΔ αϯϓϧPJ͋Γ·͢ https://bit.ly/3cVCXCt ͳ͔ͬͨͷͰPull RequestΛग़ͯ͠ࡌͤͯΒͬͨ
Serverless Next.jsΛ͏߹ͷҙ • Lambda@edge্Ͱಈ࡞͍ͯ͠Δ͜ͱΛΕͳ͍ • ϑΝΠϧαΠζ࣮ߦ࣌ؒͷ੍ݶ͕͋Δ • FileSystemΛ͏ܥͷػೳࣄނΓ͍͢ • ϩά͕ʮΞΫηε͞ΕͨϦʔδϣϯͷCloudWatch
Logʯʹࢄ • ʮ࠷৽ͷػೳʯ͑ΔΑ͏ʹͳΔ·ͰλΠϜϥά͕ग़Δ
AWS AmplifyΛ Overwrite͢Δ import { AmplifyS3ResourceTemplate } from '@aws-amplify/cli-extensibility-helper'; export
function override( resources: AmplifyS3ResourceTemplate ) { resources.s3Bucket.versioningConfiguration = { status: "Enabled" } } • amplify override <resource> • 👍 ΧελϜ͍ͨ͠ॴ͚ͩ ɹίʔυͰมߋͰ͖Δ • 😖 શϦιʔεʹ ɹ·ͩະରԠ
AWSͰJSΛͬͯΠϯϑϥΛఆٛ͢Δ • REST API։ൃத৺ -> Serverless FW or AWS CDK
(Nodejs Construct) • ࡉ͔͘ઃఆΛॻ͖͍ͨΘ͚Ͱແ͍ -> AWS Amplify (+ amplify overrideͰAWS CDKʣ • Next.jsΛ྿Ձ͔ͭServerlessʹӡ༻͍ͨ͠ -> Serverless Next.js • ΄΅CloudFormationΛॻ͘ͷʹ͍ۙ -> AWS CDK
Agenda • WebαΠτߏஙʹ͓͚ΔΠϯϑϥͷଟ༷Խ • IaaS / FaaSͱIaCΛར༻ͨ͠JSΦϯϦʔͷΠϯϑϥఆٛ • AWSʹ͓͚ΔJSͷΈͰͷߏஙύλʔϯ3छྨ •
AWSͰfaust.js(Next.js)ͰWordPressΛར༻͢Δ߹ͷ ҙ
https://faustjs.org/
WordPress / GraphQL / Next.js -> Faust.js • WP EngineʢWPϗεςΟϯάձࣾʣ͕ϦϦʔε
• WordPressͷϑϩϯτΤϯυΛNext.jsʹ͢ΔͨΊͷFW (ݱঢ়) • WordPressཧը໘࿈ܞ • ϓϨϏϡʔػೳ • GraphQLεΩʔϚAuth༻React Hookͷఏڙ etc…
Setup npx create-next-app \ -e https://github.com/wpengine/faustjs/tree/canary \ --example-path examples/next/getting-started \
--use-npm \ my-app cd my-app
import { GetServerSidePropsContext } from 'next'; import { getNextServerSideProps }
from '@faustjs/next'; import { client } from 'client'; export default function MyPage() { const { usePosts } = client; return ( <> <h2>Recent Posts</h2> {usePosts()?.nodes.map((post) => ( <li key={post.id}>{post.title()}</li> ))} </> ); } export async function getServerSideProps(context: GetServerSidePropsContext) { return getNextServerSideProps(context, { Page: MyPage, client, }); } WPͷσʔλऔಘ • Faust.jsఏڙͷ HookؔΛ͏ • GQtyͰGraphQLͷ APIݺͼग़͠ • WPଆͷ༷Λҙࣝͤͣ͑Δ
AWSͰࢼ͢ࡍͷҙ • Node.js v14Ҏ্͕ඞਢ • Lambda@edgeAppRunnerNode v12ʢ2021/11࣌ʣ • Serverless Next.js͕ɺݱঢ়͑ͳ͍ʢAWSͷରԠͪʣ
• v12ͷEOL͕2022/4͝ΖͳͷͰɺͦͷ͏ͪv14ରԠ͢Δʢͣʣ • ͯͳ͍ਓɺFargateEC2ɾebΛ
JavaScript(TS)ͰϝσΟΞαΠτΛ࡞Δ • αʔϏεɾπʔϧͷਐԽʹΑΓɺ JS͚ͩͰ࣮ɾఆٛͰ͖ΔྖҬ͕૿͑ͨ • Construct / 3rd party libraryʹΑΓɺ
ʮதΛҙࣝͤͣʹಈ͔ͤΔڥઃఆʯೖख͕༰қʹ • ʮ0͔1͔ʯͰͳ͍ɻ͕ɺ ʮ͚ࣗͨͪͩͰͰ͖Δํ๏ʯΛ͍ͬͯΔ͜ͱॏཁ
Thanks • SNS: Twitter->@hide__dev GitHub->hideokamoto • Resources • AWS CDK:
https://aws.amazon.com/jp/cdk/ • Serverless Framework: https://www.serverless.com/framework • Serverless Next.js: https://serverless-nextjs.com/ • Faust.js: https://faustjs.org/