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
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
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
730
コミュニティ運営から 中の人に変わって感じたこと
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
250
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
気持ちぃ〜角丸 Squircle
ryu1013
0
170
JAWS-UG 東京支部が始める、JAWS-UG支部コラボ / JAWS-UG lunchtime LT Collaboration
y0hgi
0
220
R8 の設定見直しでアプリサイズ削減
andpad
0
190
Domain-Driven Transformation
hschwentner
2
2.3k
RAG の “R” を Swift で覗いてみる 〜「意味から探す」検索の仕組み〜
nao_randd
0
130
プロダクトコードからライブラリの境界を見つける
elmetal
PRO
0
110
AWSに止められる覚悟してますか?
morizo_1984
2
520
Kubernetes祭り #2 OP/ED
bells17
0
110
SREの越境 / SRE Collaboration
y0hgi
2
300
なぜCTOを降りてFDEを選んだのか?〜なぜプロダクト企業がFDEで顧客の現場に踏み込むのか〜
gonta
1
180
Compiling a real-world CLI tool to native with Static Hermes
tmikov2023
0
100
iOS 27でニュースアプリはどう変わる!? 〜日経電子版の新機能対応と、開発事例から〜
lynnswap
7
13k
Featured
See All Featured
How to build a perfect <img>
jonoalderson
1
6.1k
Digital Ethics as a Driver of Design Innovation
axbom
PRO
1
450
Testing 201, or: Great Expectations
jmmastey
46
8.3k
Bootstrapping a Software Product
garrettdimon
PRO
306
120k
My Coaching Mixtape
mlcsv
0
340
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
610
[Rails World 2026] Durable orchestration on Rails: from continuation to workflow
palkan
1
690
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
47
8.4k
Being A Developer After 40
akosma
92
590k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
320
From Legacy to Launchpad: Building Startup-Ready Communities
dugsong
0
350
Unsuck your backbone
ammeep
672
58k
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/