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
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
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
Findy - エンジニア向け会社紹介/Findy Company Deck
findyinc
6
400k
Webの地図
yosuke_furukawa
PRO
6
4.9k
thread_parallel_with_free-threaded_Python_and_NumPy.pdf
riku_sakamoto
0
370
FreeBSDでZabbixを動かす
kenkino
0
340
カツオ、ご期待ください
suneo3476
0
120
Apple Intelligence を用いた個人情報誤送信防止、及びユーザーリクエスト体験の改善について
yukiny
0
240
Heart of Swift Concurrency
koher
0
1.1k
仕様駆動開発による爆速プロダクト開発 / Bakusoku Spec Driven Development
kobakei
0
140
Herb in Rails 8.2: Your ERB views, now HTML-aware @ Rails World 2026, Austin, Texas
marcoroth
0
120
速習iPhone Duo対応
yuukiw00w
2
900
WebRTC映像をAirPlayに対応させる挑戦.pdf
monolithic_adam
0
330
C#の現在地 進化の歴史と、AI時代の.NET Everywhere
neuecc
5
4.4k
Featured
See All Featured
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
690
Ruling the World: When Life Gets Gamed
codingconduct
0
370
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6.1k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
520
The untapped power of vector embeddings
frankvandijk
2
1.9k
Producing Creativity
orderedlist
PRO
348
41k
Rebuilding a faster, lazier Slack
samanthasiow
85
9.7k
First, design no harm
axbom
PRO
2
1.3k
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
540
WCS-LA-2024
lcolladotor
0
840
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
380
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
730
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!
͓͠·͍