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
PHP製のPodCast配信用WebアプリをReact+Next.jsなSSGで作り直してみた話
Search
Kaz Watanabe
March 26, 2021
Programming
720
3
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
PHP製のPodCast配信用WebアプリをReact+Next.jsなSSGで作り直してみた話
Kaz Watanabe
March 26, 2021
More Decks by Kaz Watanabe
See All by Kaz Watanabe
開発エンジニアが取り組む DevSecOps ~ GitHub Enterprise × Azure での実践~
kaz29
0
46
Greenは本当にGreenか? - B/GデプロイとAPI自動テストで安心デプロイ
kaz29
1
200
CI/CD/IaC 久々に0から環境を作ったらこうなりました
kaz29
1
510
開発エンジニアが実践するDevSecOps
kaz29
0
170
PHPCon福岡2024-Azureもなかなかいいですよ.pdf
kaz29
2
390
Azure Container Apps + Bicep 〜 こんな感じで運用しています
kaz29
3
1.3k
20220908_フロントエンドパフォーマンス改善.pdf
kaz29
2
200
バックエンドエンジニアの私がお勧めする SPAフロントエンド開発環境
kaz29
6
6.3k
201909-PHPCon北海道-PHPでCI_CD.pdf
kaz29
0
4.2k
Other Decks in Programming
See All in Programming
難しいけど、読めた。- OSSの入口に立った話。
sts11142
0
130
ソニーのクラウド共通基盤の変遷とAI時代の開発スタイルに合わせた進化 / The Journey of Sony’s Common Cloud Platform and Its Evolution for AI-Native Development
kenjiyoneyama
0
140
市販E-Readerを乗っ取れ 〜Embedded Swiftで電子ペーパーガジェットを制御する〜
trickart
0
220
Intent as Code
shoppingjaws
6
1.1k
半永久的に提供し続けられるプライベートクラウドを目指して ― 利用者の認知負荷を抑えるAPI抽象化とハードウェア世代交代の基盤設計
tomokon
0
140
マイコン向けの軽量Ruby「PicoRuby」で各種デバイスを制御するネイティブアプリの実現手法
bash0c7
0
460
KiroのSpecで「五目並べ」を作ってみる
satoshi256kbyte
1
270
Vue Fes Japan 2026 タイムテーブル徹底解説
448jp
1
550
AgentCore CLI で進化した AWS での AI エージェントの作り方 : 必要な機能を必要な時に
icoxfog417
PRO
3
390
setup-vp GitLab対応の裏側
naokihaba
0
140
iOS 27でニュースアプリはどう変わる!? 〜日経電子版の新機能対応と、開発事例から〜
lynnswap
7
13k
thread_parallel_with_free-threaded_Python_and_NumPy.pdf
riku_sakamoto
0
360
Featured
See All Featured
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
550
SEO Brein meetup: CTRL+C is not how to scale international SEO
lindahogenes
2
2.9k
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
2
290
Fashionably flexible responsive web design (full day workshop)
malarkey
409
67k
How to Create Impact in a Changing Tech Landscape [PerfNow 2023]
tammyeverts
56
3.5k
How to optimise 3,500 product descriptions for ecommerce in one day using ChatGPT
katarinadahlin
PRO
3
3.8k
Paper Plane (Part 1)
katiecoart
PRO
2
11k
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
770
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
1
540
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.4k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
Transcript
Θͨͳ!LB[@ 1)1ͷ1PE$BTU৴༻8FCΞϓϦ Λ3FBDU /FYUKTͳ44(Ͱ࡞Γͯ͠ Έͨ !1)1FS,BJHJ
8)0 ลҰ (Θͨͳ ͔ͣͻΖ) @kaz_29 גࣜձࣾϋʔτϏʔπ ։ൃࣄۀ෦
גࣜձࣾϋʔτϏʔπ ຊۀMSPͷձࣾͰɺ ։ൃࣄۀΛ͍ͬͯ·͢ɻ IUUQTIFBSUCFBUTKQ
Agenda • PodCastͷΈ • v1(چ൛)ͷߏ • WHY? • v2Ͱ༻͢Δٕज़ཁૉ •
SPA / SSR / SSG ͱ? • v2ͷಈ࡞ڥ • ·ͱΊ
IC4","#"
Podcastͷ৴
Podcastͷ৴ w 344'FFE w গͳ͘ͱ݅ͷΤϐιʔυ w ΞʔτϫʔΫ IUUQTIFMQBQQMFDPNJUDQPEDBTUT@DPOOFDUJUDDC
Podcastͷ৴ IUUQTIFMQBQQMFDPNJUDQPEDBTUT@DPOOFDUJUDDC
v1ͷಈ࡞ڥ • PHP / Slim3 • neon • Docker •
Azure DevOps
v1ͷಈ࡞ڥ
v1ͷಈ࡞ڥ "[VSF8FC"QQGPS$POUBJOFST ίʔυΛQVTI ίϯςφΠϝʔδΛQVTI ίϯςφΠϝʔδΛQVMM 4UBHJOHTMPUΛߋ৽ຊ൪ʹEFQMPZ "[VSF%FW0QT 3FQPT1JQFMJOFT "[VSF$POUBJOFS3FHJTUPSZ αΠτΛ֬ೝ
Իσʔλ "[VSF#MPC4UPSBHF
ಛʹͳ͘ ৴Ͱ͖͍ͯΔ͕…
React SSGԽ͍ͨ͠ʂ
WHY? • ӡӦͷखؒΛͳΔ͘ݮΒ͍ͨ͠ .neonฤू͢Δͷ໘😅 • ৴ͷίετ͍ํ͕ྑ͍ SSGԽ͢Δͱ੩తͳϑΝΠϧͷΈͷ৴ͳͷͰ͍ܰ • ࣄͷҊ݅ͰɺશͳSSGͳͦ͞͏ͳͷͰݟΛಘ͍ͨ ં֯ͳͷͰҰൠʹެ։Ͱ͖ΔαΠτͰࢼ͍ͨ͠
SPA / SSR / SSG
SPA (Single Page Application / Client Side Rendering) • ϝϦοτ
• JSͷϑϨʔϜϫʔΫͰϦονͳUXΛఏڙͰ͖Δ • ϖʔδભҠ͕ૣ͍(࡞ΓʹΑΔ͕…) • σϝϦοτ • ॳظද͕͍ࣔ • SEOతʹෆར(࠷ۙͦ͏Ͱͳ͍…?)
SSR (Server Side Rendering) • ϝϦοτ • දࣔ·Ͱͷ͕࣌ؒॖͰ͖Δ αʔόͰϨϯμϦϯά͢ΔͨΊɻαʔόʔଆʹෛՙ͕͔͔Δɻ •
SEOతʹ༗ར(࠷ۙͦ͏Ͱͳ͍…?) • σϝϦοτ • nodeͷαʔό͕ඞཁ • ཧ͕໘
SSG (Server Side Generator) • ϝϦοτ • ੩తͳαΠτͳͷͰ͍ܰ • Webαʔό͚ͩͰ৴Ͱ͖Δ
• σϝϦοτ • ಈతͳϖʔδ͕ଟ͍αΠτʹ͔ͳ͍ • ߋ৽ස͕ߴ͍αΠτʹ͔ͳ͍ • ϖʔδ͕૿͑ΔͱϏϧυʹ͕͔͔࣌ؒΔ
SSG + SPA • SSG ͱ SPAͷϋΠϒϦου(?) • SSRͳ͠ͰׂΓΔ ͦͦϩάΠϯ͕ඞཁͩ͠SEOͱ͔…
• LPผͰ࡞ • ߋ৽ϖʔδ͕ଟͯ͘େৎ • WebαʔόͷΈͰ৴Մೳ(mod_rewriteతͳػೳඞਢ)
SSG Podcastͷ৴ʹ࠷దʂ
༻͢Δٕज़ཁૉ • docker / docker compose • PHP / CakePHP
4.x • swagger-php • Next.js • Typescript • Storybook • PostgreSQL • Swagger UI • Github Actions • Azure Static Web Apps (PREVIEW)
CakePHP IUUQTDBLFQIQPSH
༻͢Δٕज़ཁૉ CakePHP • 4ܥ͕ग़ͯ1Ҏ্ܦաͯ͠ɺ࠷৽4.2.3 • ܧଓͯ͠৭ʑվળ͞Ε͍ͯΔ • ·ͩ·ͩݩؾͰ͢Αʂ
Swagger UI IUUQTTXBHHFSJPUPPMTTXBHHFSVJ
༻͢Δٕज़ཁૉ Swagger UI /** * Index method * * @OA\Get(
* path="/api/articles.json", * tags={"COMMON"}, * summary="هࣄҰཡऔಘAPI", * description="هࣄҰཡऔಘAPI", * @OA\Parameter( * name="page", * in="query", * @OA\Schema( * type="integer", * ), * description="ϖʔδࢦఆ", * ), * @OA\Response( * response=200, * description="successful operation", * @OA\JsonContent( * @OA\Property( * property="success", * type="boolean", * default=true, * ), * @OA\Property( * property="data", * type="array", * @OA\Items(ref="#/components/schemas/Article"), * ), * ), * ), * ) IUUQTTXBHHFSJPUPPMTTXBHHFSVJ
༻͢Δٕज़ཁૉ Swagger UI IUUQTTXBHHFSJPUPPMTTXBHHFSVJ
༻͢Δٕज़ཁૉ Swagger UI IUUQTTXBHHFSJPUPPMTTXBHHFSVJ
ٕज़ཁૉ React • FacebookۘͷJavaScriptϑϨʔϜϫʔΫ • Angularͱ͔Vueͱ͔ͱΑ͘ൺΒΕΔ • ࠃͰVue͕ਓؾ? • ࠓճ͍׳ΕͨReactͰ͢͢Ί·͢
Storybook IUUQTTUPSZCPPLKTPSH
༻͢Δٕज़ཁૉ Storybook import React from 'react' import { Meta }
from '@storybook/react/types-6-0' import Presentational from '.' import { Article } from '~/types' import { articles } from '~/__fixture__/articles' export default { title: 'components/views/Admin/Articles' } as Meta const onSelect = (article: Article) => { console.info(article) } const onClick = () => console.info('onClick') const onSync = () => console.info('onSync') export const Default = () => <Presentational loading={false} articles={articles} onSelect={onSelect} onClick={onClick} /> IUUQTTUPSZCPPLKTPSH
༻͢Δٕज़ཁૉ Storybook IUUQTTUPSZCPPLKTPSH
Next.js IUUQTOFYUKTPSH
༻͢Δٕज़ཁૉ Next.js • RectϑϩϯτΤϯυ։ൃ༻ͷWebϑϨʔϜϫʔΫ • ॳظঢ়ଶͰSSRʹରԠ͍ͯ͠Δ Universal Javascript (Isomorphic JavaScript)
ରԠ • SSGʹରԠ͍ͯ͠Δ v9.3(2020/3)ͰSSGରԠͨ͠ • Կઃఆ͠ͳ͍ͰΑ͠ͳʹಈ͘ ຊՈ `No config needed.` Λᨳ͍ͬͯΔ ͕ɺ৭ʑઃఆͰ͖·͢ IUUQTOFYUKTPSH
Next.jsͷSSG
SSGͷղઆͷલʹ… Next.jsͷDynamic Routing • pages/article/[id].tsx {ม໊}.[js or tsx]ͳϑΝΠϧΛ༻ҙ͢Δͱ… • http://example.com/article/1
͜Μͳײ͡ͷΞΫηεΛࣗಈͰroutingͯ͘͠ΕΔػೳ ctx.query.id ͰΞΫηεՄೳ • pages/article/[id]/edit.tsx σΟϨΫτϦΛಈతʹࢦఆ͢Δ͜ͱՄೳ
SPA / SSRͷ࣌ http://example.com/article/{id} import React from 'react' import {
NextPage, NextPageContext } from 'next' type Props = { article: Article } const HogePage: NextPage<Props> = ({ article, }) => ( return <ArticleView article={article} /> ) ArticlePage.getInitialProps = async ({ query }: NextPageContext) => { const id = Number(query.id) const res = await fetch(`${process.env.NEXT_PUBLIC_API_ENDPOINT}/article/${id}.json`) const response = await res.json() return { article: response.data } } export default ArticlePage - URLʹΞΫηε͞Εͨ߹ʹαʔόʔαΠυͰಈ͘ - ϒϥβͰભҠͨ͠߹ʹΫϥΠΞϯταΠυͰಈ͘
SSG http://example.com/article/{id} type Props = { article: Article } const
ArticlePage: NextPage<Props> = ({ article, }) => { return <ArticleView article={article} /> } export const getStaticPaths: GetStaticPaths = async () => { const res = await fetch(`${process.env.NEXT_PUBLIC_API_ENDPOINT}/articles.json`) const response = await res.json() const paths: any[] = [] for (const article of response.data) { paths.push({ params: { id: String(article.id) } }) } return { paths, fallback: false, } } export const getStaticProps: GetStaticProps = async (context: GetStaticPropsContext) => { const id = context.params && context.params.id if (!id) { return { notFound: true } } const res = await fetch(`${process.env.NEXT_PUBLIC_API_ENDPOINT}/articles/${id}.json`) const response = await res.json() return { props: { article: response.data } } } export default ArticlePage ଘࡏ͢ΔϖʔδͷҰཡΛऔಘ ݸผͷϖʔδͷใΛऔಘ (getInitialPropsʹ૬)
SSG Ϗϧυ݁Ռ
SSG http://example.com/article/{id} <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_FILENAME} !-f RewriteRule
^([0-9]+)$ article/$1.html [L] </IfModule> ৴ڥͰ͜Μͳײ͡ͷઃఆ͕ඞཁ http://example.com/1 => http://example.com/article/1.html
͓·͚ (SSG + SPAͷ߹) http://example.com/article/{id} import { NextPage } from
'next' import React from 'react' import AdminLayout from '~/layouts/Admin/index' import AdminArticleView from '~/components/views/Admin/Article' import { useRouter } from 'next/router' type Props = React.ComponentProps<typeof AdminArticleView> const AdminArticlesPage: NextPage = () => { const router = useRouter() const props: Props = { article_id: Number(router.query.id), } return ( <AdminLayout> <AdminArticleView {...props} /> </AdminLayout> ) } export default AdminArticlesPage ApacheͳͲͷWebαʔόͰ৴͢Δ߹ɺ SSRͰͳ͍ͷͰαʔόαΠυͰgetInitialPropsવಈ͔ͳ͍ɻ ࣗલͰDynamic RoutingͷύϥϝʔλΛऔಘ͢Δඞཁ͕͋Δ
༻͢Δٕज़ཁૉ Azure Static Web Apps (PREVIEW) • ੩తαΠτͷϗεςΟϯάαʔϏε • GitHub
ActionsʹΑΔ Build & Deploy (GitHub࿈ܞඞਢ) • ΧελϜυϝΠϯରԠɺSSLূ໌ॻ(ແྉ) ΧελϜυϝΠϯͷઃఆΛ͢ΔͱɺࣗಈతʹSSLূ໌ॻ࡞͞ΕΔ Zone Apex(Naked Domainʣݱঢ়ඇରԠ (CloudflareͬͨΓ͢ΕରԠͰ͖Δ) • ϓϨϏϡʔػೳ PRΛ࡞͢ΔͱɺϓϨϏϡʔ൛͕ࣗಈσϓϩΠɻϚʔδ͢ΔͱϓϨϏϡʔ൛ࣗಈআ͞Εຊ൪өʹөɻ • ΞϓϦΤϯδχΞʹ͍͍͢ • ࠓͷͱ͜Ζແྉ • ϩʔΧϧڥͰ࣮ߦՄೳͳػೳ(static-web-apps-cli)͕ϦϦʔε͞Εͨ (2021/3/5 new!) • https://github.com/Azure/static-web-apps-cli IUUQTB[VSFNJDSPTPGUDPNKBKQTFSWJDFTBQQTFSWJDFTUBUJD
v2ͷಈ࡞ڥ
v2ͷಈ࡞ڥ "[VSF4UBUJD8FC"QQT (JUIVC"DUJPOT "[VSF4UBUJD8FC"QQT 13Λ࡞ (JUIVC"DUJPOT CVJMEΛ࣮ߦ σϓϩΠ ։ൃɾςετ 13
هࣄσʔλ Λಉظ هࣄσʔλΛऔಘ Իσʔλ "[VSF#MPC4UPSBHF
ಈ͔Μ😱
v2ͷಈ࡞ڥ "[VSF4UBUJD8FC"QQT (JUIVC"DUJPOT • ΤϯυϙΠϯτ͓ͦΒ͘App Service (Windows)? • ੈք֤ͷཧతʹࢄͨ͠ϙΠϯτ͔Βఏڙ͞ΕΔ https://docs.microsoft.com/ja-jp/azure/static-web-apps/overview
• Azure Traffic managerܦ༝ͰΞΫηε͞ΕΔ EJH99999:::::;;;;;;;;;B[VSFTUBUJDBQQTOFU ʜ 99999:::::;;;;;;;;;B[VSFTUBUJDBQQTOFU*/" "/48&34&$5*0/ 99999:::::;;;;;;;;;B[VSFTUBUJDBQQTOFU*/$/".&B[VSFTUBUJDBQQTUSB⒏DNBOBHFSOFU B[VSFTUBUJDBQQTUSB⒏DNBOBHFSOFU*/$/".&NTIBILTUBUJDTJUFTQSPEFBTUBTJBQB[VSFXFCTJUFTOFU ʜ ͠Μࡶه"QQ4FSWJDF4UBUJD8FC"QQTͷΈΛ୳Δʢඇެࣜʣ IUUQTCMPHTIJCBZBOKQFOUSZ ඇެࣜͳใͰ͢
mod_rewrite͑Μ😱 Կ͔ผͷํ๏ͳ͍ͷ͔ʜ
v2ͷಈ࡞ڥ "[VSF4UBUJD8FC"QQT (JUIVC"DUJPOT • routes.jsonͰ੍ޚͰ͖Δ ͨͩ͠ਖ਼نදݱͱ͔ͰࢦఆͰ͖ͳ͍ͷͰॻ͖͍͑ͨϧʔτΛશͯهड़͢Δඞཁ͕͋Δ "[VSF4UBUJD8FC"QQTϓϨϏϡʔͰͷϧʔτ IUUQTEPDTNJDSPTPGUDPNKBKQB[VSFTUBUJDXFCBQQTSPVUFT \ SPVUFT<
\ SPVUF TFSWFBSUJDMFIUNM ^ > ^ ͜ΕΛΤϐιʔυهࡌ͠ͳ͍ͱ͍͔Μ…
v2ͷಈ࡞ڥ "[VSF4UBUJD8FC"QQT (JUIVC"DUJPOT • Ϗϧυ࣌ʹҰཡϖʔδͷgetStaticPropsͰࣗಈੜ const exportRoutes = (articles: Article[])
=> { const routes = { routes: [] as any, platformErrorOverrides: [ { errorType: 'NotFound', serve: '/404.html', }, ], mimeTypes: { xml: 'application/xml; charset=UTF-8', }, } for (const article of articles) { routes.routes.push({ route: `/${article.id}`, serve: `/article/${article.id}.html`, }) } fs.writeFileSync('./public/routes.json', JSON.stringify(routes)) } export const getStaticProps: GetStaticProps = async (_) => { firebaseInit() const db = firebase.firestore() const ref = db.collection('articles') const articles: Article[] = [] try { const query = await ref.orderBy('date', 'desc').get() query.forEach(doc => { articles.push(doc.data() as Article) }) if (process.env.BUILD === '1') { exportRoutes(articles) } } catch (error) { console.error(error) } return { props: { articles, url: process.env.NEXT_PUBLIC_WEB_ENDPOINT } } }
V2 https://heartbeats.jp/hbsakaba/ #hbsakaba
·ͱΊ
·ͱΊ • SSGศར • SSR / SSG / SSG+SPA దࡐదॴͰ͍͚Δͱྑ͍
• ϑϩϯτͷ։ൃָ͍͠Αʂ • ͪΖΜۤ࿑͋Δ͚ͲɺόοΫΤϯυಉ͡😅 • ࣗͰ੍ޚͰ͖ΔΞϓϦΛ࣋ͬͯΔͱ৭ʑ࣮ݧͰ͖ͯศར Enjoy!
͓͠·͍