æè¡é¨ã®å¤æï¼@hokacchaï¼ã§ããä»åã¯ã¯ãã¯ãããã®ã¦ã§ããµã¤ãã®ããã³ãã¨ã³ãã Next.js ãªã©ã使ã£ã¦ä½ãç´ãã¦ããè©±ãæ¸ãã¾ãã
ãã®è¨äºã§ç´¹ä»ããæ°ã·ã¹ãã ã¯ãã¹ãã¼ããã©ã³åãã®ã¬ã·ããã¼ã¸ã§ç¢ºèªãããã¨ãã§ãã¾ããããèå³ãããããã¯ã¬ã·ããã¼ã¸ãã¹ãã¼ããã©ã³ã®ã¦ã¼ã¶ã¼ã¨ã¼ã¸ã§ã³ãã§éã㦠DevTools ãªã©ã§ç¢ºèªãã¦ã¿ã¦ãã ããã Next.js 㨠GraphQL ã§åãã¦ããã®ããããã¨æãã¾ãã

ãåãã®æ¹ãå¤ãããããã¾ããããã¯ãã¯ãããã®ã¦ã§ããµã¤ãã¯ã¢ããªã·ãã¯ãª Rails ã§ä½ããã¦ãã¦ã10å¹´ä»¥ä¸ Rails ã§éçºãç¶ãã¦ãã¾ããã10 年以ä¸åãã·ã¹ãã ã§éçºãéããã°å½ç¶ã¬ã¬ã·ã¼ãªé¨åã大éã«çã¾ãã¦ãã¾ãããç¹ã«ããã³ãã¨ã³ãã¯ãã®å½±é¿ãé¡èã§ããã
ã©ããã使ããã¦ãããããããªã CSS ã大éã«ãããJS ã®ã³ã¼ãã¯æãªããã® CoffeeScript*1 㨠jQuery ã§æ¸ããã¦ãããJS ã®ã©ã¤ãã©ãªã®ç®¡çã vendor/assets ã«ãã¡ã¤ã«ãå
¥ãã¦ãªãã¸ããªã«ã³ãããããã¨ããéç¨ãapp/assets 以ä¸ã®ãã¡ã¤ã«ããã£ã¬ã¯ããªåã«è¦åãè¦ç´ããªããã©ãã«ãã¡ã¤ã«ãç½®ãã¦ãããããããããããªããã¨ããç¶æ
ã§ãã
ãããã£ãç¶æ ãªã®ã§å½ç¶éçºå¹çã¯æªããªãã¾ãããéçºã®ã¢ããã¼ã·ã§ã³ãä½ããªãã¾ãããã®çµæã¦ã¼ã¶ã¼ã«ä¾¡å¤ãå±ããã¾ã§ã®ã¹ãã¼ããé ããªã£ã¦ãã¾ãã®ãæå¤§ã®åé¡ã§ãããã®åé¡ãæ ¹æ¬çã«è§£æ±ºãããããä»å Next.js ã§ããã³ãã¨ã³ããä½ãç´ãã¨ããæ±ºæããã¾ããã
ä¸åº¦ã«ãã¹ã¦ã®ç»é¢ãç½®ãæããã®ã¯ç¡ç*2ãªã®ã§ã¾ãã¯æãæ´»çºã«éçºããã¦ããã¦ã¼ã¶ã¼ããã®å©ç¨ãå¤ãã¨ããããå§ãããã¨ã«ãã¦ãã¹ãã¼ããã©ã³çã®ã¬ã·ããã¼ã¸ãã¿ã¼ã²ããã«æ±ºããä»å¹´ã®2æãããããéçºãã¯ããã¦å æãªãªã¼ã¹ãããã¨ãã§ãã¾ãããæ¯è¼çãã¾ããã£ã¦ããã®ã§ä»å¾ãé©ç¨ããç»é¢ãåºãã¦ããäºå®ã§ãã
以éã§ã¯ä»åã®ã·ã¹ãã ã®æè¡è¦ç´ ãããã©ã¼ãã³ã¹ã¸ã®å½±é¿ã«ã¤ãã¦èª¬æãã¾ãã
æè¡è¦ç´
ä»åã®ã·ã¹ãã ã«ãããéè¦ãªæè¡è¦ç´ ã¯ä»¥ä¸ã§ãã
- TypeScriptï¼è¨èªï¼
- Next.jsï¼ããã³ãã¨ã³ããã¬ã¼ã ã¯ã¼ã¯ï¼
- GraphQLï¼APIï¼
æè¡é¸æããä¸ã§ã¾ãæåã«èããã®ã¯ãTypeScript ãä¸å¿ã«æ®ãããã¨ã§ããåãã§ãã¯ã«ããæ´åæ§ã®æ¤æ»ãè£å®ããªãã¡ã¯ã¿ãªã³ã°ãä¸å¿ã¨ããã¨ãã£ã¿ã®æ¯æ´ãªã©ãTypeScript ãå°å ¥ãããã¨ã«ããçç£æ§ã®åä¸ã¯é常ã«å¤§ãããã®ãããã¾ãã
次ã«ããã³ãã¨ã³ãã®æç»ã©ã¤ãã©ãªã¯ã·ã§ã¢ã®å¤§ãããTypeScript ã¨ã®ç¸æ§ã使ãããããªã©ãèæ ®ããããã§ React ãæ¡ç¨ãããã¨ã«ãããµã¤ãã®æ§è³ªä¸ Server Side Renderingï¼ä»¥ä¸ SSRï¼ã¯å¿ è¦ã«ãªãã¨æã£ã¦ããã®ã§ãReact.js ã§ SSR ãããæãã«åããã¬ã¼ã ã¯ã¼ã¯ã¨ãããã¨ã§ Next.js ãæ¡ç¨ãã¾ãããNext.js ã¯æè¡é¸å®ãããå°ãåã« TypeScript 対å¿ãå¼·åããããåçãªã«ã¼ãã£ã³ã°ããµãã¼ããããã¨ãããæãã®ã¢ãããã¼ãããã£ãã®ã決ãæã®ä¸ã¤ã§ããã
次㫠API ã§ããã¯ãã¯ãããã«ã¯ã¢ãã¤ã«ã¢ããªãªã©ã§ä½¿ããã¦ããã社å ã§ã¯ Pantry ã¨å¼ã°ãã¦ãã REST API ã®ã·ã¹ãã ãããã¾ããæå㯠Pantry ãç´æ¥ Next.js ããå©ç¨ããæ¹åã§èãã¦ãã¾ããããèªè¨¼ã®åé¡ãããªã¯ã¨ã¹ãã»ã¬ã¹ãã³ã¹ã¸ã® TypeScript ã®åä»ãã®åé¡ãå°æ¥çã« Pantry 以å¤ã®ãµã¼ãã¹ã«ããªã¯ã¨ã¹ããå¿ è¦ã«ãªãå¯è½æ§ãèããã¨ãBFF ã¬ã¤ã¤ã¼ã« GraphQL ãå°å ¥ããã®ãããããã¨ããçµè«ã«ãªããGraphQL ãå°å ¥ãããã¨ã«ãã¾ããã
ãã®ç®è«è¦ã¯ãã¾ããããBFF ã®ã¬ã¤ã¤ã¼ã¨ã㦠GraphQL ãããæãã«åãã¦ãã¾ãããªã¯ã¨ã¹ãã»ã¬ã¹ãã³ã¹ã®åå®ç¾©ã«ã¯ graphql-codegen ãå©ç¨ãããã¨ã§ GraphQL ã®ã¹ãã¼ããã TypeScript ã®åå®ç¾©ãã¡ã¤ã«ãèªåçæãã¦ãã¾ã ãã¾ããGraphQL ã®ãµã¼ãã¼å®è£ ã TypeScript ï¼ç´ æ´ãª express-graphql ã使ã£ãå®è£ ï¼ãæ¡ç¨ããåä¸ã®è¨èªã§ããã³ãã¨ã³ã㨠API ãéçºã§ããã®ã§è¨èªã®ã³ã³ããã¹ãã¹ã¤ãããå°ãªããªãããã«ãã¦ãã¾ã*3ã
ç¾ç¶ã® GraphQL ãµã¼ãã¼ã®ä»äºã®ã»ã¨ãã©ã¯ Pantry ã¸ã®ãªã¯ã¨ã¹ãã§ãããPantry ã¸ã®ãªã¯ã¨ã¹ãã®ã¨ãããå°ã工夫ãã¦æ¥½ãããã¨ã«æåããã®ã§ç´¹ä»ãã¾ãã
Pantry 㯠Garage ã¨ãããã¬ã¼ã ã¯ã¼ã¯ãç¨ãã¦å®è£ ããã¦ãã API ãµã¼ãã¼ã§ããGarage ã«ã¯ fields ã¨ããã¯ã¨ãªã¹ããªã³ã°ã§åå¾ãããªã½ã¼ã¹ã®ãã£ã¼ã«ããçµãè¾¼ããã¨ããæ©è½ãããã¾ãã
/v1/recipes/:id?fields=id,title,user[id,name]
ãã®ãããªæãã§ããã«ã³ãåºåãã§åå¾ããããã£ã¼ã«ããæå®ããuser[id,name] ã®ããã«ãã¹ããããªã½ã¼ã¹ã®ãã£ã¼ã«ããçµãè¾¼ãã¾ãããã㯠GraphQL ã®ã¯ã¨ãªã«é常ã«ããä¼¼ã¦ãã¾ããGraphQL ã§è¡¨ç¾ããã¨æ¬¡ã®ããã«ãªãã§ãããã
query {
recipe(id: $id) {
id
title
user {
id
name
}
}
}
Garage ã® fields 㯠Facebook ã® Graph API ãåèã«ããã¦ä½ããã¦ãããGraph API ã GraphQL ã®ååã§ããã¨ããçµç·¯ã«ããããã®ãããªé¡ä¼¼ããã¤ã³ã¿ã¼ãã§ã¼ã¹ã«ãªã£ã¦ããããã§ããä»åã®ã·ã¹ãã ã§ã¯ãã®æ§è³ªãå©ç¨ããGraphQL ã®ã¯ã¨ãªã Garage ã® fields ã«èªåã§å¤æããPantry ã¸ãªã¯ã¨ã¹ãããã¨ããæ©è½ãå®è£ ãã¾ãããããã¯ãã¾ãåãã¦ããã GraphQL ã®ãµã¼ãã¼å®è£ ãå¤§å¹ ã«ç°¡ç¥åããã¾ããã
ã·ã¹ãã æ§æ
ä»åã¯ãã¹ã¦ã®ç»é¢ããªãã¬ã¤ã¹ããããã§ã¯ãªããä¸é¨ã®ç»é¢ã ãæ°ã·ã¹ãã ã«åããã®ã§ããã®å¶å¾¡ãåæ®µã®ãªãã¼ã¹ãããã·ï¼Nginxï¼ã§æ¯ãåãã¦ãã¾ãã

/recipe/:idã®ã¹ãã¼ããã©ã³ããã®ãªã¯ã¨ã¹ãã Next.js ã¸ã/graphql ã GraphQL ã®ãµã¼ãã¼ã¸ã«ã¼ãã£ã³ã°ããæ®ãã¯ããã¾ã§éãã® Rails ã®ã¢ããªã±ã¼ã·ã§ã³ã¸ã«ã¼ãã£ã³ã°ãã¾ããNext.js ã®ãµã¼ãã¼ã§ã¯ SSR ããããªããHTML ãä½ã£ã¦ã¦ã¼ã¶ã¼ã«è¿ãã¾ããGraphQL ã¸ã®ãªã¯ã¨ã¹ãã«ã¤ãã¦ã¯ãNext.js ã SSR æã« GraphQL ã® API ãå¼ã³åºãå ´åã¨ãã¯ã©ã¤ã¢ã³ãããã©ã¦ã¶ããç´æ¥ GraphQL ã® API ãå¼ã³åºãå ´åãããã¾ãã
SSR ã®æ¯éã«ã¤ãã¦ã¯è²ã ã¨è°è«ãããã§ãããããããã©ã¼ãã³ã¹ï¼ç¹ã« LCPï¼Largest Contentful Paintï¼ï¼ã®æé©åãOGP 対å¿ãªã©ãèæ ®ã㦠SSR ãæ¡ç¨ãã¦ãã¾ãã
社å ã§ã¯ Node.js ã§ãµã¼ãã¼ãéç¨ããç¥è¦ãã»ã¨ãã©ãªãã£ãã®ã§ãæ§è½ãéç¨ã®é¢ã§ä¸å®ãããã¾ãããã社å ã«ã¯ ECS ã«ããã³ã³ããã®ãããã¤åºç¤ãæ´ã£ã¦ãããDocker ã§åãããããã°ãã«ãããã»ã¹åãªã©ã¯èããã« 1vCPU ã§ã¿ã¹ã¯ã横ã«ä¸¦ã¹ãã ãã§ããã®ã§æã£ã¦ãããããæ¥½ã«éç¨ãå¯è½ã§ãããæ§è½é¢ã§ããNext.js ã® SSR ãµã¼ãã¼ã¯ 200rps å¼·ã 1vCPU ã®ã¿ã¹ã¯ 7 ã¤åå¾ã§æãã¦ããã®ã§ã¾ãã¾ãã¨ãã£ãã¨ããã§ã*4ã
ããã©ã¼ãã³ã¹
ããã©ã¼ãã³ã¹ã®å¤åã«ã¤ãã¦ãå°ã触ãã¦ããã¾ããããã³ãã¨ã³ãã®ããã©ã¼ãã³ã¹è¨æ¸¬ã«ã¯ Calibre ã¨ãããµã¼ãã¹ãå©ç¨ãã¦ããã以ä¸ã Calibre ã§ã® before/after ã§ãã
before

after

ãããè¦ã¦ãããã¨ãããããã«ãã·ã¹ãã ãå·æ°ãããã¨ã§å¤§å¹ ã«ããã©ã¼ãã³ã¹ãåä¸ãã¾ãããç¹ã« First Contentful Paint (ä»¥ä¸ FCP) ãå§åçã«éããªã£ã¦ããã®ããããã¨æãã¾ãããªããããã¯ä½éåç·ï¼ä¸è¨ã®æ°å¤ã¯ 3G åç·ç¸å½ã§è¨æ¸¬ï¼ã§ç¹ã«é¡èã§ãLTE ç¸å½ã ã¨ããå°ãå·®ã¯å°ãããªãã¾ãã

å ã®å®è£ ã® FCP ãé ãã£ãã®ã¯ã·ã¹ãã ããªãã¥ã¼ã¢ã«ããåããããã£ã¦ããåé¡ç¹ã®ã²ã¨ã¤ã§ã巨大㪠CSS ã defer ã§ããªã JS ã head ã§èªã¾ãã¦ãã¦ãã¯ãªãã£ã«ã«ã¬ã³ããªã³ã°ãã¹ã®æé©åãã§ãã¦ããªãã®ãåå ã§ããããªãã¨ããããã«ãã©ãã§èªã¾ãã¦ãããããããªã CSS ã大éã«ãã£ã¦æ¶ãã®ãé£ãããhamlï¼Rails ã® Viewï¼ã«åããã¾ãã JS ã head ã§èªã¾ãã JS ã«ä¾åãã¦ã㦠defer ã§ããªãããªã©ã®çç±ã§ FCP ã®æé©åãé£ããç¶æ ã§ããã
ã§ãã®ã§ Rails ãé ããNext.js ã ã¨éããã¨ãããã¬ã¼ã ã¯ã¼ã¯ã®å·®ã§ã¯ããã¾ãããRails ã§ãã¹ã¯ã©ããã§æ¸ãç´ãã¦ãã¥ã¼ãã³ã°ããã°åç¨åº¦ã®ããã©ã¼ãã³ã¹ã¯ã§ã¾ãããã ãNext.js ã¯ãããã£ãããã©ã¼ãã³ã¹æé©åãããç¨åº¦èªåã§ãã£ã¦ããç¹ã«ããã¦ã¯éå¸¸ã«æ¥½ã§ãããã¾ããNext.js ã«çµã¿è¾¼ã¾ãã Web Vitals ã®è¨æ¸¬æ©è½ã使ã£ã¦ Web Vitals ã®æ°å¤ãè¨é²ããããã«ããã®ã§ããã使ã£ã¦ä»å¾ãæ¹åãé²ãã¦ããäºå®ã§ããç¹ã« LCPãTTIï¼Time To Interactiveï¼ ãããã¯ããå°ãã©ãã«ããããã§ããã
ã¾ã¨ã
Next.js ã GraphQL ã使ã£ã¦ã¦ã§ããµã¤ãã®ããã³ãã¨ã³ãã®ã·ã¹ãã ãå·æ°ãã¦ããè©±ãæ¸ãã¾ãããä»å¾ãé©ç¨ç¯å²ãåºãã¦ãããéçºã®çç£æ§ãããããã¨ã§ã¦ã¼ã¶ã¼ã«å±ãã価å¤ãæå¤§åãã¦ããã¾ããã¾ããä»åæ¸ãã以å¤ã«ããèªè¨¼ããã®ã³ã°ãã¨ã©ã¼ãã©ããã³ã°ãCSS in JSãæç»ã®ããã©ã¼ãã³ã¹æé©åãA/B ãã¹ããªã©è²ã ã¨é¢ç½ãç¥è¦ãæºã¾ã£ã¦ããã®ã§ã¾ãå¥ã®æ©ä¼ã«å ±æãããã¨æãã¾ãã
æå¾ã«ãã¯ãã¯ãããã§ã¯ã¢ãã³ãªããã³ãã¨ã³ãã®åºç¤ãä½ã£ã¦ããä»äºãããã®åºç¤ã使ã£ã¦ãµã¼ãã¹éçºããä»äºã大éã«ããã¾ãï¼åå®ï¼ãããå°ãã§ãèå³ãããã°ãæ°è»½ã«ãåãåãããã ããï¼
*1:CoffeeScript ã¯ä»åã®è©±ã¨ã¯å¥ã®ããã¸ã§ã¯ãã§ãªãããã¨ã«æåãã¾ããããã®è©±ã¯ã¾ãå¥ã®æ©ä¼ã«ã
*2:調ã¹ããç¾æç¹ã§1600以ä¸ã®ã«ã¼ãã£ã³ã°ãããã¾ãã
*3:Pantry ãå§ãã¨ããããã¯ã¨ã³ãã® API ãµã¼ãã¼ã¯ Ruby ãªã®ã§ããã夿´ããå¿ è¦ãããã°å½ç¶ã³ã³ããã¹ãã¹ã¤ããã¯çºçãã¾ããã
*4:GraphQL ã®ãµã¼ãã¼ã¯å¥ã