Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
JavaScript(TypeScript)で メディアサイトを インフラから構築する方法 /...
Search
Hidetaka Okamoto
November 27, 2021
Programming
4.5k
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
740
コミュニティ運営から 中の人に変わって感じたこと
hideokamoto
0
160
Developerが Developer Advocateになった話 / dev-rel-meetup-tokyo-71
hideokamoto
0
400
Jamstack開発者のための App Runner入門
hideokamoto
1
560
WordPressでの webサイト制作2022 / ngk2022s
hideokamoto
0
500
AWS上でStripeを利用したアプリをより安全にデプロイする方法 /jaws-pankration-2021
hideokamoto
1
260
Shifter Headlessと Headless WordPressの紹介
hideokamoto
0
2.3k
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
R8 の設定見直しでアプリサイズ削減
andpad
0
220
Findy - エンジニア向け会社紹介/Findy Company Deck
findyinc
6
400k
作れる、でも消せるか?──安全に「枯らして消す」機能削除術 / Drain, Then Delete
chobishiba
2
720
sbt 2.0: takeoff
eed3si9n
0
160
なぜCTOを降りてFDEを選んだのか?〜なぜプロダクト企業がFDEで顧客の現場に踏み込むのか〜
gonta
1
210
外から家のPCの電源入れたい!をPHPで解決する
kawashimo0
1
140
contenteditable と日本語入力に向き合う
colorful12
0
200
Workers Cache を知る
syumai
0
370
SalesForceを内製化!? ~ HR事業を支える顧客管理基盤のインフラを大公開 ~
oku053
0
180
動作中のプログラムの中身をリアルタイムに覗く / Realtime Debugger for CSharp with Roslyn
prota
1
1.9k
Railsバージョンを4倍にした話
optim
0
400
Long Living Tokens, Die, Die, Die! (or: The One Ring is Banned in Modern)
sylph01
0
310
Featured
See All Featured
Between Models and Reality
mayunak
4
500
Winning Ecommerce Organic Search in an AI Era - #searchnstuff2025
aleyda
2
2.2k
Designing for Performance
lara
612
70k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
3.1k
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
2
2.9k
Six Lessons from altMBA
skipperchong
29
4.5k
Balancing Empowerment & Direction
lara
6
1.3k
From π to Pie charts
rasagy
1
400
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
2
330
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
560
Ethics towards AI in product and experience design
skipperchong
2
430
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
530
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/