PageSourceSearch

https://portfolio.falcon0296.workers.dev/posts

html portfolio.falcon0296.workers.dev collected 2026-10-03 08:09:16 UTC 88,124 bytes, 2 lines download raw bytes

1<!DOCTYPE html><html lang="ja-JP"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="preload" as="image" href="/profile.png"/>
vendor: 66 bytes, line 1
1<script async="" src="https://www.googletagmanager.com/gtag/js?id=
1G-YT1Y3HC2ES
vendor: 11 bytes, line 1
1"></script>
1<title>Posts | dashi&#x27;s portfolio</title><meta name="description" content="dashi&#x27;s portfolio site."/><link rel="modulepreload" href="/assets/main-BTvl8QpF.js"/><link rel="modulepreload" href="/assets/index-CQvDxl4U.js"/><link rel="modulepreload" href="/assets/CardBody-DxlayHGn.js"/><link rel="modulepreload" href="/assets/PageTitle-DlVZgDBS.js"/><link rel="modulepreload" href="/assets/Chip-B8qCU3-V.js"/><link rel="modulepreload" href="/assets/dayjs-AduHta9m.js"/><link rel="icon" type="image/x-icon" href="/favicon/favicon.ico"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png"/><link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png"/><link rel="manifest" href="/favicon/site.webmanifest"/><link rel="stylesheet" href="/assets/styles-CY6_D8i6.css"/><link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Source+Code+Pro:wght@400;700&amp;display=swap"/><link rel="stylesheet" href="/assets/main-BJ75wo_X.css" type="text/css"/>
1<script>
vendor: 117 bytes, line 1
1window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('js',new Date());gtag('config','
1G-YT1Y3HC2ES
vendor: 3 bytes, line 1
1');
1</script>
1</head><body class="bg-black" style="font-family:&#x27;Source Code Pro&#x27;, monospace"><div class="relative min-h-screen max-w-2xl mx-auto px-2 pt-4 pb-12 lg:py-16"><a href="/"><img class="rounded-full" src="/profile.png" height="120" width="120" alt="profile image"/></a><a href="/"><div class="Typewriter" data-testid="typewriter-wrapper"></div></a><div class="text-slate-400"><div class="Typewriter" data-testid="typewriter-wrapper"></div></div><div class="border-b border-slate-700 my-10"></div><!--$--><h1 class="text-xl font-bold text-slate-50 pb-4"><div class="flex items-center"><div class="Typewriter" data-testid="typewriter-wrapper"></div><a href="/posts/feed" target="_blank" rel="noopener noreferrer" class="text-lg ml-2"><svg stroke="currentColor" fill="currentColor" stroke-width="0" role="img" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg"><path d="M19.199 24C19.199 13.467 10.533 4.8 0 4.8V0c13.165 0 24 10.835 24 24h-4.801zM3.291 17.415c1.814 0 3.293 1.479 3.293 3.295 0 1.813-1.485 3.29-3.301 3.29C1.47 24 0 22.526 0 20.71s1.475-3.294 3.291-3.295zM15.909 24h-4.665c0-6.169-5.075-11.245-11.244-11.245V8.09c8.727 0 15.909 7.184 15.909 15.91z"></path></svg></a></div></h1><section><div class="grid grid-cols-1 gap-2"><a href="/posts/migrate-to-tanstack-start" class="flex flex-col justify-between border border-slate-600 rounded-lg p-2 bg-slate-800 min-h-20"><div class="text-slate-50 tracking-wider pb-2">ポートフォリオをNext.jsからTanStack Start + Cloudflareに移行した</div><div class="flex justify-between items-end gap-2 text-sm"><time class="text-slate-400 shrink-0 whitespace-nowrap">7 months ago</time><div class="flex flex-wrap justify-end gap-1"><span class="px-3 py-1 rounded-full text-gray-500 bg-gray-200 font-semibold text-sm flex align-center w-max active:bg-gray-300 transition duration-300 ease mr-1">TanStack Start</span><span class="px-3 py-1 rounded-full text-gray-500 bg-gray-200 font-semibold text-sm flex align-center w-max active:bg-gray-300 transition duration-300 ease mr-1">Cloudflare</span><span class="px-3 py-1 rounded-full text-gray-500 bg-gray-200 font-semibold text-sm flex align-center w-max active:bg-gray-300 transition duration-300 ease mr-1">Next.js</span></div></div></a><a href="/posts/nextjs-booking" class="flex flex-col justify-between border border-slate-600 rounded-lg p-2 bg-slate-800 min-h-20"><div class="text-slate-50 tracking-wider pb-2">できるだけお金をかけずに宿泊施設の予約システムを作ってみた</div><div class="flex justify-between items-end gap-2 text-sm"><time class="text-slate-400 shrink-0 whitespace-nowrap">4 years ago</time><div class="flex flex-wrap justify-end gap-1"><span class="px-3 py-1 rounded-full text-gray-500 bg-gray-200 font-semibold text-sm flex align-center w-max active:bg-gray-300 transition duration-300 ease mr-1">Next.js</span><span class="px-3 py-1 rounded-full text-gray-500 bg-gray-200 font-semibold text-sm flex align-center w-max active:bg-gray-300 transition duration-300 ease mr-1">React</span></div></div></a><a href="/posts/next-api-og-image" class="flex flex-col justify-between border border-slate-600 rounded-lg p-2 bg-slate-800 min-h-20"><div class="text-slate-50 tracking-wider pb-2">Next.jsブログでOGP画像を動的生成して返すAPI Routesを実装してみた</div><div class="flex justify-between items-end gap-2 text-sm"><time class="text-slate-400 shrink-0 whitespace-nowrap">4 years ago</time><div class="flex flex-wrap justify-end gap-1"><span class="px-3 py-1 rounded-full text-gray-500 bg-gray-200 font-semibold text-sm flex align-center w-max active:bg-gray-300 transition duration-300 ease mr-1">Next.js</span><span class="px-3 py-1 rounded-full text-gray-500 bg-gray-200 font-semibold text-sm flex align-center w-max active:bg-gray-300 transition duration-300 ease mr-1">React</span></div></div></a><a href="/posts/start-my-nextjs-blog" class="flex flex-col justify-between border border-slate-600 rounded-lg p-2 bg-slate-800 min-h-20"><div class="text-slate-50 tracking-wider pb-2">面倒くさがりがNext.jsでブログを構築してみた</div><div class="flex justify-between items-end gap-2 text-sm"><time class="text-slate-400 shrink-0 whitespace-nowrap">5 years ago</time><div class="flex flex-wrap justify-end gap-1"><span class="px-3 py-1 rounded-full text-gray-500 bg-gray-200 font-semibold text-sm flex align-center w-max active:bg-gray-300 transition duration-300 ease mr-1">Next.js</span><span class="px-3 py-1 rounded-full text-gray-500 bg-gray-200 font-semibold text-sm flex align-center w-max active:bg-gray-300 transition duration-300 ease mr-1">React</span></div></div></a></div></section>
1<script></script>
1<!--/$--><footer class="absolute bottom-0 left-0 right-0 text-center text-sm text-slate-400 pb-2">© 2022 dashi</footer></div>
1<script src="https://embed.zenn.studio/js/window-post-message-proxy-1.0.0.js" defer=""></script>
1<script class="$tsr" id="$tsr-stream-barrier">(self.$R=self.$R||{})["tsr"]=[];self.$_TSR={h(){this.hydrated=!0,this.c()},e(){this.streamEnded=!0,this.c()},c(){this.hydrated&&this.streamEnded&&(delete self.$_TSR,delete self.$R.tsr)},p(e){this.initialized?e():this.buffer.push(e)},buffer:[]};
2;$_TSR.router=($R=>$R[0]={manifest:$R[1]={routes:$R[2]={__root__:$R[3]={preloads:$R[4]=["/assets/main-BTvl8QpF.js"],assets:$R[5]=[$R[6]={tag:"link",attrs:$R[7]={rel:"stylesheet",href:"/assets/main-BJ75wo_X.css",type:"text/css"}},$R[8]={tag:"script",attrs:$R[9]={type:"module",async:!0},children:"import(\"/assets/main-BTvl8QpF.js\")"}]},"/":$R[10]={assets:$R[11]=[$R[12]={tag:"link",attrs:$R[13]={rel:"stylesheet",href:"/assets/main-BJ75wo_X.css",type:"text/css"}}]},"/about":$R[14]={assets:$R[15]=[$R[16]={tag:"link",attrs:$R[17]={rel:"stylesheet",href:"/assets/main-BJ75wo_X.css",type:"text/css"}}]},"/experience":$R[18]={assets:$R[19]=[$R[20]={tag:"link",attrs:$R[21]={rel:"stylesheet",href:"/assets/main-BJ75wo_X.css",type:"text/css"}}]},"/zenn":$R[22]={assets:$R[23]=[$R[24]={tag:"link",attrs:$R[25]={rel:"stylesheet",href:"/assets/main-BJ75wo_X.css",type:"text/css"}}]},"/posts/$slug":$R[26]={assets:$R[27]=[$R[28]={tag:"link",attrs:$R[29]={rel:"stylesheet",href:"/assets/main-BJ75wo_X.css",type:"text/css"}}]},"/posts/":$R[30]={preloads:$R[31]=["/assets/index-CQvDxl4U.js","/assets/CardBody-DxlayHGn.js","/assets/PageTitle-DlVZgDBS.js","/assets/Chip-B8qCU3-V.js","/assets/dayjs-AduHta9m.js"],assets:$R[32]=[$R[33]={tag:"link",attrs:$R[34]={rel:"stylesheet",href:"/assets/main-BJ75wo_X.css",type:"text/css"}}]}}}
2,matches:$R[35]=[$R[36]={i:"__root__�",u:1791014956805,s:"success",ssr:!0},$R[37]={i:"�posts��posts�",u:1791014956805,s:"success",l:$R[38]=[$R[39]={title:"ポートフォリオをNext.jsからTanStack Start + Cloudflareに移行した",slug:"migrate-to-tanstack-start",excerpt:"このポートフォリオサイトをNext.js 14 (App Router) + VercelからTanStack Start + Cloudflare Workersに移行した。その記録をまとめておく。\n 移行の動機\n特に大きな不満があったわけではないが、TanStack Startに興味があり試してみたかった。Cloudflareは料金面での魅力もあり、合わせて移行を決めた。\n 移行前の構成\n\nフレームワーク: Next.js 14 App Router\nデプロイ: Vercel\n主要ライブラリ: @vercel/og(OG画像)、@t3-oss/env-nextjs(環境変数)、zenn-markdown-html(Markdownパース)\n\n TanStack Startについて\nTanStack Startは、TanStack Routerをベースにしたフルスタックフレームワーク。ビルドツールとしてvinxiを使っており、ViteベースなのでViteエコシステムの恩恵をそのまま受けられる。\nデータフェッチはRSC(React Server Components)ではなく、ルートの",contentHtml:"\x3Cp data-line=\"1\" class=\"code-line\">このポートフォリオサイトをNext.js 14 (App Router) + VercelからTanStack Start + Cloudflare Workersに移行した。その記録をまとめておく。\x3C/p>\n\x3Ch2 id=\"%E7%A7%BB%E8%A1%8C%E3%81%AE%E5%8B%95%E6%A9%9F\" data-line=\"3\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E7%A7%BB%E8%A1%8C%E3%81%AE%E5%8B%95%E6%A9%9F\" aria-hidden=\"true\">\x3C/a> 移行の動機\x3C/h2>\n\x3Cp data-line=\"5\" class=\"code-line\">特に大きな不満があったわけではないが、TanStack Startに興味があり試してみたかった。Cloudflareは料金面での魅力もあり、合わせて移行を決めた。\x3C/p>\n\x3Ch2 id=\"%E7%A7%BB%E8%A1%8C%E5%89%8D%E3%81%AE%E6%A7%8B%E6%88%90\" data-line=\"7\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E7%A7%BB%E8%A1%8C%E5%89%8D%E3%81%AE%E6%A7%8B%E6%88%90\" aria-hidden=\"true\">\x3C/a> 移行前の構成\x3C/h2>\n\x3Cul data-line=\"9\" class=\"code-line\">\n\x3Cli data-line=\"9\" class=\"code-line\">\x3Cstrong>フレームワーク\x3C/strong>: Next.js 14 App Router\x3C/li>\n\x3Cli data-line=\"10\" class=\"code-line\">\x3Cstrong>デプロイ\x3C/strong>: Vercel\x3C/li>\n\x3Cli data-line=\"11\" class=\"code-line\">\x3Cstrong>主要ライブラリ\x3C/strong>: \x3Ccode>@vercel/og\x3C/code>(OG画像)、\x3Ccode>@t3-oss/env-nextjs\x3C/code>(環境変数)、\x3Ccode>zenn-markdown-html\x3C/code>(Markdownパース)\x3C/li>\n\x3C/ul>\n\x3Ch2 id=\"tanstack-start%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6\" data-line=\"13\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#tanstack-start%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6\" aria-hidden=\"true\">\x3C/a> TanStack Startについて\x3C/h2>\n\x3Cp data-line=\"15\" class=\"code-line\">TanStack Startは、TanStack Routerをベースにしたフルスタックフレームワーク。ビルドツールとして\x3Ca href=\"https://github.com/nksaraf/vinxi\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">vinxi\x3C/a>を使っており、ViteベースなのでViteエコシステムの恩恵をそのまま受けられる。\x3C/p>\n\x3Cp data-line=\"17\" class=\"code-line\">データフェッチはRSC(React Server Components)ではなく、ルートのloaderとサーバー関数(\x3Ccode>createServerFn\x3C/code>)で行う。\x3C/p>\n\x3Ch2 id=\"%E8%A9%B0%E3%81%BE%E3%81%A3%E3%81%9F%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%E3%81%A8%E8%A7%A3%E6%B1%BA%E7%AD%96\" data-line=\"19\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E8%A9%B0%E3%81%BE%E3%81%A3%E3%81%9F%E3%83%9D%E3%82%A4%E3%83%B3%E3%83%88%E3%81%A8%E8%A7%A3%E6%B1%BA%E7%AD%96\" aria-hidden=\"true\">\x3C/a> 詰まったポイントと解決策\x3C/h2>\n\x3Ch3 id=\"%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%E5%95%8F%E9%A1%8C\" data-line=\"21\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84%E5%95%8F%E9%A1%8C\" aria-hidden=\"true\">\x3C/a> ファイルシステムアクセスができない問題\x3C/h3>\n\x3Cp data-line=\"23\" class=\"code-line\">最大の課題はMarkdownファイルの読み込み。元の実装は\x3Ccode>fs.readdirSync\x3C/code>や\x3Ccode>fs.readFileSync\x3C/code>でファイルを読んでいたが、Cloudflare Workersではランタイムでのファイルシステムアクセスができない。\x3C/p>\n\x3Cp data-line=\"25\" class=\"code-line\">\x3Cstrong>解決策\x3C/strong>: Viteの\x3Ccode>import.meta.glob\x3C/code>を使ってビルド時にバンドルする。\x3C/p>\n\x3Cdiv class=\"code-block-container\">\x3Cdiv class=\"code-block-filename-container\">\x3Cspan class=\"code-block-filename\">libs/posts.ts\x3C/span>\x3C/div>\x3Cpre class=\"shiki github-dark\" style=\"background-color:#151e2c;color:#e1e4e8\">\x3Ccode class=\"code-line\" data-line=\"27\">\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">
2const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> rawFiles\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#F97583\"> import\x3C/span>\x3Cspan style=\"color:#E1E4E8\">.\x3C/span>\x3Cspan style=\"color:#79B8FF\">meta\x3C/span>\x3Cspan style=\"color:#E1E4E8\">.\x3C/span>\x3Cspan style=\"color:#B392F0\">glob\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"../_posts/*.md\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">, {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  query: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"?raw\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  import: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"default\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  eager: \x3C/span>\x3Cspan style=\"color:#79B8FF\">true\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">}) \x3C/span>\x3Cspan style=\"color:#F97583\">as\x3C/span>\x3Cspan style=\"color:#B392F0\"> Record\x3C/span>\x3Cspan style=\"color:#E1E4E8\">&lt;\x3C/span>\x3Cspan style=\"color:#79B8FF\">string\x3C/span>\x3Cspan style=\"color:#E1E4E8\">, \x3C/span>\x3Cspan style=\"color:#79B8FF\">string\x3C/span>\x3Cspan style=\"color:#E1E4E8\">&gt;;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\x3C/code>\x3C/pre>\x3C/div>\x3Cp data-line=\"35\" class=\"code-line\">\x3Ccode>import.meta.glob\x3C/code>はViteがビルド時に展開してくれるため、ランタイムでのファイルシステムアクセスが不要になる。\x3Ccode>gray-matter\x3C/code>や\x3Ccode>zenn-markdown-html\x3C/code>も含めてすべてWorkerバンドルに含まれる。\x3C/p>\n\x3Ch3 id=\"%40vercel%2Fog-%E3%81%8C%E4%BD%BF%E3%81%88%E3%81%AA%E3%81%84%E5%95%8F%E9%A1%8C\" data-line=\"37\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%40vercel%2Fog-%E3%81%8C%E4%BD%BF%E3%81%88%E3%81%AA%E3%81%84%E5%95%8F%E9%A1%8C\" aria-hidden=\"true\">\x3C/a> \x3Ccode>@vercel/og\x3C/code> が使えない問題\x3C/h3>\n\x3Cp data-line=\"39\" class=\"code-line\">OG画像生成に使っていた\x3Ccode>@vercel/og\x3C/code>はVercel専用。Cloudflare WorkersではSVG生成ライブラリの\x3Ccode>satori\x3C/code>とWASMベースのSVG→PNG変換ライブラリ\x3Ccode>@resvg/resvg-wasm\x3C/code>を組み合わせて代替した。\x3C/p>\n\x3Cdiv class=\"code-block-container\">\x3Cdiv class=\"code-block-filename-container\">\x3Cspan class=\"code-block-filename\">routes/api/-og.tsx\x3C/span>\x3C/div>\x3Cpre class=\"shiki github-dark\" style=\"background-color:#151e2c;color:#e1e4e8\">\x3Ccode class=\"code-line\" data-line=\"41\">\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">import\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> satori \x3C/span>\x3Cspan style=\"color:#F97583\">from\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> \"satori\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">import\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> { Resvg, initWasm } \x3C/span>\x3Cspan style=\"color:#F97583\">from\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> \"@resvg/resvg-wasm\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> svg\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#F97583\"> await\x3C/span>\x3Cspan style=\"color:#B392F0\"> satori\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(&lt;\x3C/span>\x3Cspan style=\"color:#B392F0\">div\x3C/span>\x3Cspan style=\"color:#E1E4E8\">&gt;\x3C/span>\x3Cspan style=\"color:#F97583\">...&lt;/\x3C/span>\x3Cspan style=\"color:#E1E4E8\">div\x3C/span>\x3Cspan style=\"color:#F97583\">&gt;\x3C/span>\x3Cspan style=\"color:#E1E4E8\">, {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  width: \x3C/span>\x3Cspan style=\"color:#79B8FF\">1200\x3C/span>\x3Cspan style=\"color:#E1E4E8\">
2,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  height: \x3C/span>\x3Cspan style=\"color:#79B8FF\">630\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  fonts: [{ name: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"custom\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">, data: fontData, style: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"normal\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> }],\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">});\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> resvg\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#F97583\"> new\x3C/span>\x3Cspan style=\"color:#B392F0\"> Resvg\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(svg, { fitTo: { mode: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"width\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">, value: \x3C/span>\x3Cspan style=\"color:#79B8FF\">1200\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> } });\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> png\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> resvg.\x3C/span>\x3Cspan style=\"color:#B392F0\">render\x3C/span>\x3Cspan style=\"color:#E1E4E8\">().\x3C/span>\x3Cspan style=\"color:#B392F0\">asPng\x3C/span>\x3Cspan style=\"color:#E1E4E8\">();\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\x3C/code>\x3C/pre>\x3C/div>\x3Ch3 id=\"%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%81%AE%E7%A7%BB%E8%A1%8C\" data-line=\"55\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E3%83%AB%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%81%AE%E7%A7%BB%E8%A1%8C\" aria-hidden=\"true\">\x3C/a> ルーティングの移行\x3C/h3>\n\x3Cp data-line=\"57\" class=\"code-line\">Next.js App RouterとTanStack Startのルーティングは構造が似ているが記法が異なる。\x3C/p>\n\x3Ctable data-line=\"59\" class=\"code-line\">\n\x3Cthead data-line=\"59\" class=\"code-line\">\n\x3Ctr data-line=\"59\" class=\"code-line\">\n\x3Cth>Next.js\x3C/th>\n\x3Cth>TanStack Start\x3C/th>\n\x3C/tr>\n\x3C/thead>\n\x3Ctbody data-line=\"61\" class=\"code-line\">\n\x3Ctr data-line=\"61\" class=\"code-line\">\n\x3Ctd>\x3Ccode>app/layout.tsx\x3C/code>\x3C/td>\n\x3Ctd>\x3Ccode>routes/__root.tsx\x3C/code>\x3C/td>\n\x3C/tr>\n\x3Ctr data-line=\"62\" class=\"code-line\">\n\x3Ctd>\x3Ccode>app/page.tsx\x3C/code>\x3C/td>\n\x3Ctd>\x3Ccode>routes/index.tsx\x3C/code>\x3C/td>\n\x3C/tr>\n\x3Ctr data-line=\"63\" class=\"code-line\">\n\x3Ctd>\x3Ccode>app/posts/[slug]/page.tsx\x3C/code>\x3C/td>\n\x3Ctd>\x3Ccode>routes/posts/$slug.tsx\x3C/code>\x3C/td>\n\x3C/tr>\n\x3Ctr data-line=\"64\" class=\"code-line\">\n\x3Ctd>\x3Ccode>app/api/og/route.tsx\x3C/code>\x3C/td>\n\x3Ctd>\x3Ccode>routes/api/-og.tsx\x3C/code>\x3C/td>\n\x3C/tr>\n\x3C/tbody>\n\x3C/table>\n\x3Cp data-line=\"66\" class=\"code-line\">APIルート(\x3Ccode>createAPIFileRoute\x3C/code>使用)はTanStack Routerのルートツリーに含まれないため、ファイル名に\x3Ccode>-\x3C/code>プレフィックスをつけることで警告を消せる。\x3C/p>\n\x3Ch3 id=\"%E3%83%87%E3%83%BC%E3%82%BF%E3%83%95%E3%82%A7%E3%83%83%E3%83%81%E3%81%AE%E7%A7%BB%E8%A1%8C\" data-line=\"68\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E3%83%87%E3%83%BC%E3%82%BF%E3%83%95%E3%82%A7%E3%83%83%E3%83%81%E3%81%AE%E7%A7%BB%E8%A1%8C\" aria-hidden=\"true\">\x3C/a> データフェッチの移行\x3C/h3>\n\x3Cp data-line=\"70\" class=\"code-line\">Next.jsのRSCでのデータフェッチは、TanStack Startではルートの\x3Ccode>loader\x3C/code>と\x3Ccode>createServerFn\x3C/code>
2で代替する。\x3C/p>\n\x3Cdiv class=\"code-block-container\">\x3Cpre class=\"shiki github-dark\" style=\"background-color:#151e2c;color:#e1e4e8\">\x3Ccode class=\"code-line\" data-line=\"72\">\x3Cspan class=\"line\">\x3Cspan style=\"color:#a0aab5\">// Next.js (RSC)\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">export\x3C/span>\x3Cspan style=\"color:#F97583\"> default\x3C/span>\x3Cspan style=\"color:#F97583\"> async\x3C/span>\x3Cspan style=\"color:#F97583\"> function\x3C/span>\x3Cspan style=\"color:#B392F0\"> Page\x3C/span>\x3Cspan style=\"color:#E1E4E8\">() {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">  const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> data\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#F97583\"> await\x3C/span>\x3Cspan style=\"color:#B392F0\"> fetchSomething\x3C/span>\x3Cspan style=\"color:#E1E4E8\">();\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">  return\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> &lt;\x3C/span>\x3Cspan style=\"color:#B392F0\">Component\x3C/span>\x3Cspan style=\"color:#B392F0\"> data\x3C/span>\x3Cspan style=\"color:#E1E4E8\">={\x3C/span>\x3Cspan style=\"color:#FFAB70\">data\x3C/span>\x3Cspan style=\"color:#E1E4E8\">} /&gt;;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">}\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#a0aab5\">// TanStack Start\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> fetchSomething\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#B392F0\"> createServerFn\x3C/span>\x3Cspan style=\"color:#E1E4E8\">({ method: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"GET\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> }).\x3C/span>\x3Cspan style=\"color:#B392F0\">handler\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3Cspan style=\"color:#F97583\">async\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> () \x3C/span>\x3Cspan style=\"color:#F97583\">=&gt;\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">  return\x3C/span>\x3Cspan style=\"color:#B392F0\"> fetchSomething\x3C/span>\x3Cspan style=\"color:#E1E4E8\">();\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">});\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">export\x3C/span>\x3Cspan style=\"color:#F97583\"> const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> Route\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#B392F0\"> createFileRoute\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"/\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">)({\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#B392F0\">  loader\x3C/span>\x3Cspan style=\"color:#E1E4E8\">: () \x3C/span>\x3Cspan style=\"color:#F97583\">=&gt;\x3C/span>\x3Cspan style=\"color:#B392F0\"> fetchSomething\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(),\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#B392F0\">  component\x3C/span>\x3Cspan style=\"color:#E1E4E8\">: () \x3C/span>\x3Cspan style=\"color:#F97583\">=&gt;\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">    const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> data\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> Route.\x3C/span>\x3Cspan style=\"color:#B392F0\">useLoaderData\x3C/span>\x3Cspan style=\"color:#E1E4E8\">();\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">    return\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> &lt;\x3C/span>\x3Cspan style=\"color:#B392F0\">Component\x3C/span>\x3Cspan style=\"color:#B392F0\"> data\x3C/span>\x3Cspan style=\"color:#E1E4E8\">={\x3C/span>\x3Cspan style=\"color:#FFAB70\">data\x3C/span>\x3Cspan style=\"color:#E1E4E8\">} /&gt;;
2\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  },\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">});\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\x3C/code>\x3C/pre>\x3C/div>\x3Cp data-line=\"93\" class=\"code-line\">\x3Ccode>createServerFn\x3C/code>のバリデーション用メソッドはドキュメントによっては\x3Ccode>.validator()\x3C/code>と書いてあるが、インストールしたバージョン(1.166.2)では\x3Ccode>.inputValidator()\x3C/code>が正しかった。バージョンによって異なるようなので注意が必要。\x3C/p>\n\x3Ch3 id=\"%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0\" data-line=\"95\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0\" aria-hidden=\"true\">\x3C/a> 環境変数\x3C/h3>\n\x3Cp data-line=\"97\" class=\"code-line\">Next.jsの\x3Ccode>NEXT_PUBLIC_*\x3C/code>プレフィックスはViteの\x3Ccode>VITE_*\x3C/code>プレフィックスに変更。\x3Ccode>@t3-oss/env-nextjs\x3C/code>は\x3Ccode>@t3-oss/env-core\x3C/code>
2に置き換えた。\x3C/p>\n\x3Cdiv class=\"code-block-container\">\x3Cdiv class=\"code-block-filename-container\">\x3Cspan class=\"code-block-filename\">env.ts\x3C/span>\x3C/div>\x3Cpre class=\"shiki github-dark\" style=\"background-color:#151e2c;color:#e1e4e8\">\x3Ccode class=\"code-line\" data-line=\"99\">\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">import\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> { createEnv } \x3C/span>\x3Cspan style=\"color:#F97583\">from\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> \"@t3-oss/env-core\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">import\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> { z } \x3C/span>\x3Cspan style=\"color:#F97583\">from\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> \"zod\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">export\x3C/span>\x3Cspan style=\"color:#F97583\"> const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> env\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#B392F0\"> createEnv\x3C/span>\x3Cspan style=\"color:#E1E4E8\">({\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  clientPrefix: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"VITE_\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  client: {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">    VITE_SITE_URL: z.\x3C/span>\x3Cspan style=\"color:#B392F0\">string\x3C/span>\x3Cspan style=\"color:#E1E4E8\">().\x3C/span>\x3Cspan style=\"color:#B392F0\">url\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(),\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">    VITE_SITE_TITLE: z.\x3C/span>\x3Cspan style=\"color:#B392F0\">string\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(),\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">    VITE_GA_ID: z.\x3C/span>\x3Cspan style=\"color:#B392F0\">string\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(),\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">    VITE_TWITTER_USER_NAME: z.\x3C/span>\x3Cspan style=\"color:#B392F0\">string\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(),\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  },\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  runtimeEnv: \x3C/span>\x3Cspan style=\"color:#F97583\">import\x3C/span>\x3Cspan style=\"color:#E1E4E8\">.\x3C/span>\x3Cspan style=\"color:#79B8FF\">meta\x3C/span>\x3Cspan style=\"color:#E1E4E8\">.env,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">});\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\x3C/code>\x3C/pre>\x3C/div>\x3Ch3 id=\"next%2Flink%E3%83%BBnext%2Fimage%E3%81%AE%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88\" data-line=\"115\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#next%2Flink%E3%83%BBnext%2Fimage%E3%81%AE%E7%BD%AE%E3%81%8D%E6%8F%9B%E3%81%88\" aria-hidden=\"true\">\x3C/a> next/link・next/imageの置き換え\x3C/h3>\n\x3Cp data-line=\"117\" class=\"code-line\">\x3Ccode>next/link\x3C/code>はTanStack Routerの\x3Ccode>Link\x3C/code>コンポーネントに、\x3Ccode>next/image\x3C/code>は通常の\x3Ccode>&lt;img&gt;\x3C/code>
2タグに置き換えた。Cloudflare PagesにはVercelのような自動画像最適化機能はないため、必要であれば別途対応が必要。\x3C/p>\n\x3Cdiv class=\"code-block-container\">\x3Cpre class=\"shiki github-dark\" style=\"background-color:#151e2c;color:#e1e4e8\">\x3Ccode class=\"code-line\" data-line=\"119\">\x3Cspan class=\"line\">\x3Cspan style=\"color:#a0aab5\">// Before\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">import\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> Link \x3C/span>\x3Cspan style=\"color:#F97583\">from\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> \"next/link\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">&lt;\x3C/span>\x3Cspan style=\"color:#E1E4E8\">Link href\x3C/span>\x3Cspan style=\"color:#F97583\">=\x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"/about\"\x3C/span>\x3Cspan style=\"color:#F97583\">&gt;\x3C/span>\x3Cspan style=\"color:#E1E4E8\">About\x3C/span>\x3Cspan style=\"color:#F97583\">&lt;/\x3C/span>\x3Cspan style=\"color:#E1E4E8\">Link\x3C/span>\x3Cspan style=\"color:#F97583\">&gt;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#a0aab5\">// After\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">import\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> { Link } \x3C/span>\x3Cspan style=\"color:#F97583\">from\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> \"@tanstack/react-router\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">&lt;\x3C/span>\x3Cspan style=\"color:#E1E4E8\">Link to\x3C/span>\x3Cspan style=\"color:#F97583\">=\x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"/about\"\x3C/span>\x3Cspan style=\"color:#F97583\">&gt;\x3C/span>\x3Cspan style=\"color:#E1E4E8\">About\x3C/span>\x3Cspan style=\"color:#F97583\">&lt;/\x3C/span>\x3Cspan style=\"color:#E1E4E8\">Link\x3C/span>\x3Cspan style=\"color:#F97583\">&gt;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\x3C/code>\x3C/pre>\x3C/div>\x3Ch2 id=\"%E3%82%B9%E3%82%AD%E3%83%A3%E3%83%95%E3%82%A9%E3%83%BC%E3%83%AB%E3%83%89%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6\" data-line=\"129\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E3%82%B9%E3%82%AD%E3%83%A3%E3%83%95%E3%82%A9%E3%83%BC%E3%83%AB%E3%83%89%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6\" aria-hidden=\"true\">\x3C/a> スキャフォールドについて\x3C/h2>\n\x3Cp data-line=\"131\" class=\"code-line\">TanStack Startは\x3Ccode>@tanstack/cli\x3C/code>でスキャフォールドできる。Cloudflareアダプターも\x3Ccode>--deployment\x3C/code>オプションで指定できる。\x3C/p>\n\x3Cdiv class=\"code-block-container\">\x3Cpre class=\"shiki github-dark\" style=\"background-color:#151e2c;color:#e1e4e8\">\x3Ccode class=\"code-line\" data-line=\"133\">\x3Cspan class=\"line\">\x3Cspan style=\"color:#B392F0\">pnpm\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> dlx\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> @tanstack/cli@latest\x3C/span>\x3Cspan style=\"color:#9ECBFF\">
2 create\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> my-app\x3C/span>\x3Cspan style=\"color:#79B8FF\"> \\\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#79B8FF\">  --framework\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> React\x3C/span>\x3Cspan style=\"color:#79B8FF\"> \\\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#79B8FF\">  --deployment\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> cloudflare\x3C/span>\x3Cspan style=\"color:#79B8FF\"> \\\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#79B8FF\">  --package-manager\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> pnpm\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\x3C/code>\x3C/pre>\x3C/div>\x3Cp data-line=\"140\" class=\"code-line\">生成されたプロジェクトには\x3Ccode>wrangler.jsonc\x3C/code>が含まれており、\x3Ccode>nodejs_compat\x3C/code>フラグもデフォルトで有効になっている。\x3Ccode>pnpm deploy\x3C/code>でそのままCloudflare Workersにデプロイできる。\x3C/p>\n\x3Ch2 id=\"%E3%81%BE%E3%81%A8%E3%82%81\" data-line=\"142\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E3%81%BE%E3%81%A8%E3%82%81\" aria-hidden=\"true\">\x3C/a> まとめ\x3C/h2>\n\x3Cp data-line=\"144\" class=\"code-line\">TanStack Startはまだ発展途上のフレームワークだが、Viteベースで動作が速く、ルーターの型安全性も高い。Cloudflare Workersへのデプロイもシンプルに設定できた。\x3C/p>\n\x3Cp data-line=\"146\" class=\"code-line\">ただし、ファイルシステムアクセスやNode.js固有のAPIに依存している箇所は移行時に注意が必要。今回はMarkdownの読み込みを\x3Ccode>import.meta.glob\x3C/code>でビルド時バンドルに変換することで対処した。\x3C/p>\n",tags:$R[40]=["TanStack Start","Cloudflare","Next.js"],date:"2026-03-08T00:00:00.000Z"},$R[41]={title:"できるだけお金をかけずに宿泊施設の予約システムを作ってみた",slug:"nextjs-booking",excerpt:" 作ったもの\nhttps://github.com/dashi296/nextjs-bookinghttps://github.com/dashi296/nextjs-booking\nhttps://nextjs-booking-sample.netlify.app/https://nextjs-booking-sample.netlify.app/\n 要件\n宿泊施設のホームページを構築し、下記を満たす予約システムを構築する\n\nカレンダーを表示し、予約が埋まっている日程は色を変える\n予約にはチェックイン日時、チェックアウト日時をPOSTする。予約直後にカレンダーに予約を反映する\nairbnbやbooking.comなどの別サービスで予約が発生した場合もある程度、カレンダー上で同期したい。\n\n 構成\n Next.js\nフロントエンドはもちろん、API Routesを使ってバックエンドも構築。\n\nVercelは商用利用の場合、$20/month かかるので 商用利用でも無料で運用できるNetlifyにデプロイした。\n\nhttps://nextjs.org/https://nextjs.or",contentHtml:"\x3Ch2 id=\"%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE\" data-line=\"1\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE\" aria-hidden=\"true\">\x3C/a> 作ったもの\x3C/h2>\n\x3Cp data-line=\"2\" class=\"code-line\">\x3Ca href=\"https://github.com/dashi296/nextjs-booking\" rel=\"noreferrer noopener nofollow\" target=\"_blank\">https://github.com/dashi296/nextjs-booking\x3C/a>\x3Ca href=\"https://github.com/dashi296/nextjs-booking\" style=\"display:none\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">https://github.com/dashi296/nextjs-booking\x3C/a>\x3C/p>\n\x3Cp data-line=\"4\" class=\"code-line\">\x3Ca href=\"https://nextjs-booking-sample.netlify.app/\" rel=\"noreferrer noopener nofollow\" target=\"_blank\">https://nextjs-booking-sample.netlify.app/\x3C/a>\x3Ca href=\"https://nextjs-booking-sample.netlify.app/\" style=\"display:none\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">https://nextjs-booking-sample.netlify.app/\x3C/a>\x3C/p>\n\x3Ch2 id=\"%E8%A6%81%E4%BB%B6\" data-line=\"7\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E8%A6%81%E4%BB%B6\" aria-hidden=\"true\">\x3C/a> 要件\x3C/h2>\n\x3Cp data-line=\"8\" class=\"code-line\">宿泊施設のホームページを構築し、下記を満たす予約システムを構築する\x3C/p>\n\x3Cul data-line=\"9\" class=\"code-line\">\n\x3Cli data-line=\"9\" class=\"code-line\">カレンダーを表示し、予約が埋まっている日程は色を変える\x3C/li>\n\x3Cli data-line=\"10\" class=\"code-line\">予約にはチェックイン日時、チェックアウト日時をPOSTする。予約直後にカレンダーに予約を反映する\x3C/li>\n\x3Cli data-line=\"11\" class=\"code-line\">airbnbやbooking.comなどの別サービスで予約が発生した場合もある程度、カレンダー上で同期したい。\x3C/li>\n\x3C/ul>\n\x3Ch2 id=\"%E6%A7%8B%E6%88%90\" data-line=\"13\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E6%A7%8B%E6%88%90\" aria-hidden=\"true\">\x3C/a> 構成\x3C/h2>\n\x3Ch3 id=\"next.js\" data-line=\"14\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#next.js\" aria-hidden=\"true\">\x3C/a> Next.js\x3C/h3>\n\x3Cp data-line=\"15\" class=\"code-line\">フロントエンドはもちろん、API Routesを使ってバックエンドも構築。\x3Cbr />\nVercelは商用利用の場合、$20/month かかるので 商用利用でも無料で運用できるNetlifyにデプロイした。\x3Cbr style=\"display:none\" />\n\x3Ca href=\"https://nextjs.org/\" rel=\"noreferrer noopener nofollow\" target=\"_blank\">https://nextjs.org/\x3C/a>\x3Ca href=\"https://nextjs.org/\" style=\"display:none\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">https://nextjs.org/\x3C/a>\x3C/p>\n\x3Ch3 id=\"google-calendar\" data-line=\"19\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#google-calendar\" aria-hidden=\"true\">\x3C/a> Google Calendar\x3C/h3>\n\x3Cp data-line=\"20\" class=\"code-line\">データベース代わりに予定を管理するために使用。\x3Cbr />\nairbnbやbooking.comのicalのURLをカレンダーに設定することで、Google Calendar上にて予約の一元管理が可能。\x3Cbr />\nGCPのサービスアカウントを使うとGoogle Calendar API経由で予約を取得・作成することが可能。\x3Cbr style=\"display:none\" />\n\x3Ca href=\"https://calendar.google.com/\" rel=\"noreferrer noopener nofollow\" target=\"_blank\">https://calendar.google.com/\x3C/a>\x3Ca href=\"https://calendar.google.com/\" style=\"display:none\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">https://calendar.google.com/\x3C/a>\x3C/p>\n\x3Ch3 id=\"upstash\" data-line=\"25\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#upstash\" aria-hidden=\"true\">\x3C/a> Upstash\x3C/h3>\n\x3Cp data-line=\"26\" class=\"code-line\">サーバーレスなRedisのデータベースを提供するサービス。\x3Cbr />
2\nGoogle Calendar APIはレスポンスが早くないので定期的にGoogle Calendar APIから予約一覧をキャッシュし、素早い予約一覧のレスポンスを実現させるために使用\x3Cbr style=\"display:none\" />\n\x3Ca href=\"https://upstash.com/\" rel=\"noreferrer noopener nofollow\" target=\"_blank\">https://upstash.com/\x3C/a>\x3Ca href=\"https://upstash.com/\" style=\"display:none\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">https://upstash.com/\x3C/a>\x3C/p>\n\x3Ch2 id=\"%E8%A8%AD%E8%A8%88\" data-line=\"30\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E8%A8%AD%E8%A8%88\" aria-hidden=\"true\">\x3C/a> 設計\x3C/h2>\n\x3Cp data-line=\"31\" class=\"code-line\">要件を満たすためにAPI Routesでは下記の3つの処理を行うエンドポイントを実装することにした。\x3C/p>\n\x3Cul data-line=\"32\" class=\"code-line\">\n\x3Cli data-line=\"32\" class=\"code-line\">予約一覧取得\x3C/li>\n\x3Cli data-line=\"33\" class=\"code-line\">予約一覧更新(定期的に叩かれるエンドポイント)\x3C/li>\n\x3Cli data-line=\"34\" class=\"code-line\">予約\x3C/li>\n\x3C/ul>\n\x3Ch2 id=\"%E5%87%A6%E7%90%86%E5%9B%B3%E8%A7%A3\" data-line=\"36\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E5%87%A6%E7%90%86%E5%9B%B3%E8%A7%A3\" aria-hidden=\"true\">\x3C/a> 処理図解\x3C/h2>\n\x3Ch3 id=\"%E4%BA%88%E7%B4%84%E4%B8%80%E8%A6%A7%E5%8F%96%E5%BE%97\" data-line=\"38\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E4%BA%88%E7%B4%84%E4%B8%80%E8%A6%A7%E5%8F%96%E5%BE%97\" aria-hidden=\"true\">\x3C/a> 予約一覧取得\x3C/h3>\n\x3Cp data-line=\"39\" class=\"code-line\">素早いレスポンスを実現するためにUpstashから予約一覧を取得する処理のみを行う\x3Cbr />\n\x3Cimg src=\"https://storage.googleapis.com/zenn-user-upload/5c061b7bfd69-20220809.png\" alt=\"\" class=\"md-img\" loading=\"lazy\" />\x3C/p>\n\x3Ch3 id=\"%E4%BA%88%E7%B4%84%E4%B8%80%E8%A6%A7%E6%9B%B4%E6%96%B0\" data-line=\"42\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E4%BA%88%E7%B4%84%E4%B8%80%E8%A6%A7%E6%9B%B4%E6%96%B0\" aria-hidden=\"true\">\x3C/a> 予約一覧更新\x3C/h3>\n\x3Cp data-line=\"43\" class=\"code-line\">予約一覧取得で取得するデータを更新するエンドポイント\x3Cbr />\n今回はGoogle Cloud Schedulerで5分に1回更新するようにしている。\x3Cbr />\n\x3Cimg src=\"https://storage.googleapis.com/zenn-user-upload/64f6ec813fa5-20220809.png\" alt=\"\" class=\"md-img\" loading=\"lazy\" />\x3C/p>\n\x3Ch3 id=\"%E4%BA%88%E7%B4%84\" data-line=\"47\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E4%BA%88%E7%B4%84\" aria-hidden=\"true\">\x3C/a> 予約\x3C/h3>\n\x3Cp data-line=\"48\" class=\"code-line\">予約時はGoogle Calendar API経由で予約を作成。\x3Cbr />\n作成時に予約した情報が返されるので、その情報を使ってUpstashのキャッシュを更新し、予約直後でも自分の予約が返されるようにした。\x3Cbr />\n\x3Cimg src=\"https://storage.googleapis.com/zenn-user-upload/c766dd7231af-20220809.png\" alt=\"\" class=\"md-img\" loading=\"lazy\" />\x3C/p>\n\x3Ch2 id=\"%E5%95%8F%E9%A1%8C%E7%82%B9\" data-line=\"52\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E5%95%8F%E9%A1%8C%E7%82%B9\" aria-hidden=\"true\">\x3C/a> 問題点\x3C/h2>\n\x3Cul data-line=\"53\" class=\"code-line\">\n\x3Cli data-line=\"53\" class=\"code-line\">現在のGoogle Calendarはデフォルトの設定では\x3Ccode>airbnb\x3C/code> や \x3Ccode>booking.com\x3C/code>などのicalを使った予約の同期を1日に1回程度しか実施しない。\x3Cbr />\n下記のリポジトリのスクリプトを使うことでその頻度を上げられるらしい。要実証。\x3C/li>\n\x3C/ul>\n\x3Cp data-line=\"56\" class=\"code-line\">\x3Ca href=\"https://github.com/derekantrican/GAS-ICS-Sync\" rel=\"noreferrer noopener nofollow\" target=\"_blank\">https://github.com/derekantrican/GAS-ICS-Sync\x3C/a>\x3Ca href=\"https://github.com/derekantrican/GAS-ICS-Sync\" style=\"display:none\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">https://github.com/derekantrican/GAS-ICS-Sync\x3C/a>\x3C/p>\n",tags:$R[42]=["Next.js","React"],date:"2022-08-09T06:03:27.175Z"},$R[43]={title:"Next.jsブログでOGP画像を動的生成して返すAPI Routesを実装してみた",slug:"next-api-og-image",excerpt:"このブログの記事をSNSで共有するときに、OGP画像を設定してオシャレに見ã
2›ãŸã„欲が出てきました。\n\nただ記事1つ1つにOGP画像を設定するなんて面倒なことはしたく無いので、ZennやQiitaのように記事のタイトルを使ってOGP画像を動的に生成することにしました。\n\nNext.jsにはAWSのLambdaを使ってエンドポイントを実装するAPI Routesという機能があり、その機能を使ってOGP画像を返すエンドポイントを作成するnext-api-og-imageというライブラリがあったのでそちらを使って実装してみました。\nhttps://github.com/neg4n/next-api-og-imagehttps://github.com/neg4n/next-api-og-image\nこちらのライブラリのREADME通りに導入し、下記のファイルを書いて実装しました。\npages/api/ogp.tsx\x3Ccode class=\"code-line\" data-line=\"9\">\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">import",contentHtml:"\x3Cp data-line=\"1\" class=\"code-line\">このブログの記事をSNSで共有するときに、OGP画像を設定してオシャレに見せたい欲が出てきました。\x3Cbr />\nただ記事1つ1つにOGP画像を設定するなんて面倒なことはしたく無いので、ZennやQiitaのように記事のタイトルを使ってOGP画像を動的に生成することにしました。\x3Cbr />\nNext.jsにはAWSのLambdaを使ってエンドポイントを実装するAPI Routesという機能があり、その機能を使ってOGP画像を返すエンドポイントを作成する\x3Ccode>next-api-og-image\x3C/code>
2というライブラリがあったのでそちらを使って実装してみました。\x3C/p>\n\x3Cp data-line=\"5\" class=\"code-line\">\x3Ca href=\"https://github.com/neg4n/next-api-og-image\" rel=\"noreferrer noopener nofollow\" target=\"_blank\">https://github.com/neg4n/next-api-og-image\x3C/a>\x3Ca href=\"https://github.com/neg4n/next-api-og-image\" style=\"display:none\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">https://github.com/neg4n/next-api-og-image\x3C/a>\x3C/p>\n\x3Cp data-line=\"7\" class=\"code-line\">こちらのライブラリのREADME通りに導入し、下記のファイルを書いて実装しました。\x3C/p>\n\x3Cdiv class=\"code-block-container\">\x3Cdiv class=\"code-block-filename-container\">\x3Cspan class=\"code-block-filename\">pages/api/ogp.tsx\x3C/span>\x3C/div>\x3Cpre class=\"shiki github-dark\" style=\"background-color:#151e2c;color:#e1e4e8\">\x3Ccode class=\"code-line\" data-line=\"9\">\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">import\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> { withOGImage } \x3C/span>\x3Cspan style=\"color:#F97583\">from\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> 'next-api-og-image'\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">import\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> { getPostSlugs, getPostBySlug } \x3C/span>\x3Cspan style=\"color:#F97583\">from\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> '../../lib/api'\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">enum\x3C/span>\x3Cspan style=\"color:#B392F0\"> QueryParams\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">  'slug'\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">}\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">export\x3C/span>\x3Cspan style=\"color:#F97583\"> default\x3C/span>\x3Cspan style=\"color:#B392F0\">
2 withOGImage\x3C/span>\x3Cspan style=\"color:#E1E4E8\">&lt;\x3C/span>\x3Cspan style=\"color:#9ECBFF\">'query'\x3C/span>\x3Cspan style=\"color:#E1E4E8\">, \x3C/span>\x3Cspan style=\"color:#F97583\">keyof\x3C/span>\x3Cspan style=\"color:#F97583\"> typeof\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> QueryParams&gt;({\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  strategy: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">'query'\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  template: {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#B392F0\">    react\x3C/span>\x3Cspan style=\"color:#E1E4E8\">: ({ \x3C/span>\x3Cspan style=\"color:#FFAB70\">slug\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> }\x3C/span>\x3Cspan style=\"color:#F97583\">:\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> { \x3C/span>\x3Cspan style=\"color:#FFAB70\">slug\x3C/span>\x3Cspan style=\"color:#F97583\">:\x3C/span>\x3Cspan style=\"color:#79B8FF\"> string\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> }) \x3C/span>\x3Cspan style=\"color:#F97583\">=&gt;\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">      const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> allSlugs\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#B392F0\"> getPostSlugs\x3C/span>\x3Cspan style=\"color:#E1E4E8\">().\x3C/span>\x3Cspan style=\"color:#B392F0\">map\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3Cspan style=\"color:#FFAB70\">s\x3C/span>\x3Cspan style=\"color:#F97583\"> =&gt;\x3C/span>\x3Cspan style=\"color:#E1E4E8\">
2 s.\x3C/span>\x3Cspan style=\"color:#B392F0\">replace\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3Cspan style=\"color:#9ECBFF\">/\x3C/span>\x3Cspan style=\"color:#85E89D;font-weight:bold\">\\.\x3C/span>\x3Cspan style=\"color:#DBEDFF\">md\x3C/span>\x3Cspan style=\"color:#F97583\">$\x3C/span>\x3Cspan style=\"color:#9ECBFF\">/\x3C/span>\x3Cspan style=\"color:#E1E4E8\">, \x3C/span>\x3Cspan style=\"color:#9ECBFF\">''\x3C/span>\x3Cspan style=\"color:#E1E4E8\">))\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">      const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> isExistSlug\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> allSlugs.\x3C/span>\x3Cspan style=\"color:#B392F0\">includes\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(slug)\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">      const\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> { \x3C/span>\x3Cspan style=\"color:#79B8FF\">title\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> } \x3C/span>\x3Cspan style=\"color:#F97583\">=\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> isExistSlug \x3C/span>\x3Cspan style=\"color:#F97583\">?\x3C/span>\x3Cspan style=\"color:#B392F0\"> getPostBySlug\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(slug, [\x3C/span>\x3Cspan style=\"color:#9ECBFF\">'title'\x3C/span>\x3Cspan style=\"color:#E1E4E8\">]) \x3C/span>\x3Cspan style=\"color:#F97583\">:\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> { title: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">'Not Found'\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> }\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#a0aab5\">      // タイトルの文字のみのフォントを取得\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">      const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> style\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> `\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">        @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP&amp;display=swap&amp;text=${\x3C/span>\x3Cspan style=\"color:#E1E4E8\">title\x3C/span>\x3Cspan style=\"color:#9ECBFF\">}');\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">        @font-face {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">          font-style:  normal;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">          font-weight: normal;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">        }\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">        body {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">          font-family: 'Noto Sans JP', sans-serif;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">        }\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">        .container {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">          width: 100vw;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">          height: 100vh;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">          display: flex;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">          flex-direction: column;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">          justify-content: center;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">          align-items: center;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">        }\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">      `\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">      return\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> (\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">        &lt;
2\x3C/span>\x3Cspan style=\"color:#85E89D\">html\x3C/span>\x3Cspan style=\"color:#E1E4E8\">&gt;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">          &lt;\x3C/span>\x3Cspan style=\"color:#85E89D\">head\x3C/span>\x3Cspan style=\"color:#E1E4E8\">&gt;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">            &lt;\x3C/span>\x3Cspan style=\"color:#85E89D\">style\x3C/span>\x3Cspan style=\"color:#B392F0\"> dangerouslySetInnerHTML\x3C/span>\x3Cspan style=\"color:#F97583\">=\x3C/span>\x3Cspan style=\"color:#E1E4E8\">{{ __html: style }} /&gt;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">          &lt;/\x3C/span>\x3Cspan style=\"color:#85E89D\">head\x3C/span>\x3Cspan style=\"color:#E1E4E8\">&gt;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">          &lt;\x3C/span>\x3Cspan style=\"color:#85E89D\">body\x3C/span>\x3Cspan style=\"color:#E1E4E8\">&gt;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">            &lt;\x3C/span>\x3Cspan style=\"color:#85E89D\">div\x3C/span>\x3Cspan style=\"color:#B392F0\"> className\x3C/span>\x3Cspan style=\"color:#F97583\">=\x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"container\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">&gt;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">              &lt;\x3C/span>\x3Cspan style=\"color:#85E89D\">h1\x3C/span>\x3Cspan style=\"color:#E1E4E8\">&gt;{title}&lt;/\x3C/span>\x3Cspan style=\"color:#85E89D\">h1\x3C/span>\x3Cspan style=\"color:#E1E4E8\">&gt;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">            &lt;/\x3C/span>\x3Cspan style=\"color:#85E89D\">div\x3C/span>\x3Cspan style=\"color:#E1E4E8\">&gt;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">          &lt;/\x3C/span>\x3Cspan style=\"color:#85E89D\">body\x3C/span>\x3Cspan style=\"color:#E1E4E8\">&gt;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">        &lt;/\x3C/span>\x3Cspan style=\"color:#85E89D\">
2html\x3C/span>\x3Cspan style=\"color:#E1E4E8\">&gt;\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">      )\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">    },\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  },\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  dev: {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">    inspectHtml: \x3C/span>\x3Cspan style=\"color:#79B8FF\">false\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  },\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">})\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\x3C/code>\x3C/pre>\x3C/div>\x3Cp data-line=\"66\" class=\"code-line\">記事のslugをapiのquery経由で渡し、一致した記事のタイトルを使った画像を生成して返すエンドポイントが実装できました。\x3C/p>\n\x3Cp data-line=\"68\" class=\"code-line\">slugが一致する記事が存在しない場合は404のレスポンスを返したかったのですが、ライブラリを使って404レスポンスを返す方法がわからなかったので、とりあえず記事タイトル名の代わりに\x3Ccode>Not Found\x3C/code>と書かれた画像を返すようにしました。\x3C/p>\n\x3Cp data-line=\"70\" class=\"code-line\">あとは記事のOGP画像先を\x3Ccode>https://owndomain/api/ogp?slug={slug}\x3C/code>という形で指定してあげれば、下記のような形でOGP画像が設定できます。\x3C/p>\n\x3Cp data-line=\"72\" class=\"code-line\">\x3Cimg src=\"/assets/posts/next-api-og-image/2022-04-18-01-41-32.png\" alt=\"\" class=\"md-img\" loading=\"lazy\" />\x3C/p>\n\x3Ch3 id=\"next-api-og-image%E3%82%92%E4%BD%BF%E3%81%86%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88\" data-line=\"74\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#next-api-og-image%E3%82%92%E4%BD%BF%E3%81%86%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88\" aria-hidden=\"true\">\x3C/a> next-api-og-imageを使うメリット\x3C/h3>\n\x3Cp data-line=\"76\" class=\"code-line\">API Routesを使うだけなので、別のプロジェクトを用意する必要がなく、スピーディーに実装できました。\x3Cbr />\nまた、生成する画像もReactのコンポーネントを書く形でスタイリングができるのはとても良いです。\x3C/p>\n\x3Ch3 id=\"next-api-og-image%E3%82%92%E4%BD%BF%E3%81%86%E3%83%87%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88\" data-line=\"79\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#next-api-og-image%E3%82%92%E4%BD%BF%E3%81%86%E3%83%87%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88\" aria-hidden=\"true\">\x3C/a> next-api-og-imageを使うデメリット\x3C/h3>\n\x3Cul data-line=\"81\" class=\"code-line\">\n\x3Cli data-line=\"81\" class=\"code-line\">API Routesが動かない環境では使えなくなる。\x3C/li>\n\x3Cli data-line=\"82\" class=\"code-line\">API RoutesはAWSのLambdaで処理を行う関係上、関数と関連リソースを50MB以内にする必要があり、\x3Ccode>next-api-og-image\x3C/code>は内部で\x3Ccode>chrome-aws-lambda\x3C/code>を使っており、そのライブラリが49.7MB(\x3Ccode>v10.1.0\x3C/code>の場合)で、重い日本語フォントはWeb経由で取得する方法を取らざるを得なかったため、豆腐が表示される可能性がある。\x3C/li>\n\x3Cli data-line=\"83\" class=\"code-line\">画像生成が負荷が高い処理であるためレスポンスまで少し時間がかかってしまい、初回リクエストの場合は正常にOGP画像を載せられない場合がある。\x3Cbr />\n(2回目以降のリクエストの場合には結果がキャッシュされるため問題なくなる。)\x3C/li>\n\x3C/ul>\n\x3Cp data-line=\"86\" class=\"code-line\">現状はとりあえず使っていこうと思っていますが、デメリットもまあまああるので、より良い方法を探していくつもりです。\x3Cbr />\nOGP画像のデザインもオシャレにしたい。\x3C/p>\n",tags:$R[44]=["Next.js","React"],date:"2022-04-17T15:34:34.954Z"},$R[45]={title:"面倒くさがりがNext.jsでブログを構築してみた",slug:"start-m
2y-nextjs-blog",excerpt:"自分の技術力向上と学んだことのアウトプットの場としてブログをはじめてみた。\n\n自分は面倒くさがりすぎるので、ここ2〜3年くらい、半年に1回くらいの頻度でブログを構築したいと思っては完成できなかったり、記事が書けなかったりで挫折してきた。\n\n同じ轍を踏まないためにある程度記事書けるような環境が構築できたら、記事を書きながらカスタムして完成を目指していくという方針にした。\n 要件\n挫折を繰り返さないために、記事を書くコストは限りなく小さくしたい。\n\n具体的には下記を満たしたい。\n\nMarkdownで書きたい\nある程度自分でブログをカスタマイズできるようにReactで書ける\n画像の挿入もショートカットコマンド一発でやりたい\ngitで管理してmainブランチにpushされたら本番に反映\n記事を作るときにyarnコマンドでテンプレートを作成できる\n\nNext.jsを使うと大体満たせそうだったので、Next.jsで構築することにしました。\n 構築\nNext.jsでブログ構築する際には、公式のexamplesにblog-starter-typescriptがあるのでそのまま使いました。\n\ntailw",contentHtml:"\x3Cp data-line=\"1\" class=\"code-line\">自分の技術力向上と学んだことのアウトプットの場としてブログをはじめてみた。\x3Cbr />\n自分は面倒くさがりすぎるので、ここ2〜3年くらい、半年に1回くらいの頻度でブログを構築したいと思っては完成できなかったり、記事が書けなかったりで挫折してきた。\x3Cbr />\n同じ轍を踏まないためにある程度記事書けるような環境が構築できたら、記事を書きながらカスタムして完成を目指していくという方針にした。\x3C/p>\n\x3Ch2 id=\"%E8%A6%81%E4%BB%B6\" data-line=\"5\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E8%A6%81%E4%BB%B6\" aria-hidden=\"true\">\x3C/a> 要件\x3C/h2>\n\x3Cp data-line=\"7\" class=\"code-line\">挫折を繰り返さないために、記事を書くコストは限りなく小さくしたい。\x3Cbr />\n具体的には下記を満たしたい。\x3C/p>\n\x3Cul data-line=\"10\" class=\"code-line\">\n\x3Cli data-line=\"10\" class=\"code-line\">Markdownで書きたい\x3C/li>\n\x3Cli data-line=\"11\" class=\"code-line\">ある程度自分でブログをカスタマイズできるようにReactで書ける\x3C/li>\n\x3Cli data-line=\"12\" class=\"code-line\">画像の挿入もショートカットコマンド一発でやりたい\x3C/li>\n\x3Cli data-line=\"13\" class=\"code-line\">gitで管理してmainブランチにpushされたら本番に反映\x3C/li>\n\x3Cli data-line=\"14\" class=\"code-line\">記事を作るときにyarnコマンドでテンプレートを作成できる\x3C/li>\n\x3C/ul>\n\x3Cp data-line=\"16\" class=\"code-line\">Next.jsを使うと大体満たせそうだったので、Next.jsで構築することにしました。\x3C/p>\n\x3Ch2 id=\"%E6%A7%8B%E7%AF%89\" data-line=\"18\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E6%A7%8B%E7%AF%89\" aria-hidden=\"true\">\x3C/a> 構築\x3C/h2>\n\x3Cp data-line=\"20\" class=\"code-line\">Next.jsでブログ構築する際には、公式のexamplesに\x3Ccode>blog-starter-typescript\x3C/code>があるのでそのまま使いました。\x3Cbr />\ntailwindcssで構築されていたのもありがたく、カスタマイズしやすいです。\x3Cbr style=\"display:none\" />\n\x3Ca href=\"https://github.com/vercel/next.js/tree/canary/examples/blog-starter-typescript\" rel=\"noreferrer noopener nofollow\" target=\"_blank\">https://github.com/vercel/next.js/tree/canary/examples/blog-starter-typescript\x3C/a>\x3Ca href=\"https://github.com/vercel/next.js/tree/canary/examples/blog-starter-typescript\" style=\"display:none\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">https://github.com/vercel/next.js/tree/canary/examples/blog-starter-typescript\x3C/a>\x3C/p>\n\x3Cp data-line=\"24\" class=\"code-line\">構築時のバージョンは下記の通りです。\x3C/p>\n\x3Ctable data-line=\"25\" class=\"code-line\">\n\x3Cthead data-line=\"25\" class=\"code-line\">\n\x3Ctr data-line=\"25\" class=\"code-line\">\n\x3Cth>Library\x3C/th>\n\x3Cth>Version\x3C/th>\n\x3C/tr>\n\x3C/thead>\n\x3Ctbody data-line=\"27\" class=\"code-line\">\n\x3Ctr data-line=\"27\" class=\"code-line\">\n\x3Ctd>next\x3C/td>\n\x3Ctd>12.1.1\x3C/td>\n\x3C/tr>\n\x3Ctr data-line=\"28\" class=\"code-line\">\n\x3Ctd>zenn-markdown-html\x3C/td>\n\x3Ctd>0.1.108\x3C/td>\n\x3C/tr>\n\x3Ctr data-line=\"29\" class=\"code-line\">\n\x3Ctd>zenn-embed-elements\x3C/td>\n\x3Ctd>0.1.108\x3C/td>\n\x3C/tr>\n\x3Ctr data-line=\"30\" class=\"code-line\">\n\x3Ctd>zenn-content-css\x3C/td>\n\x3Ctd>0.1.108\x3C/td>\n\x3C/tr>\n\x3C/tbody>\n\x3C/table>\n\x3Ch3 id=\"%E8%A8%98%E4%BA%8B%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AE%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AA%E3%83%B3%E3%82%B0\" data-line=\"32\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E8%A8%98%E4%BA%8B%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%AE%E3%82%B9%E3%82%BF%E3%82%A4%E3%83%AA%E3%83%B3%E3%82%B0\" aria-hidden=\"true\">\x3C/a> 記事ページのスタイリング\x3C/h3>\n\x3Cp data-line=\"34\" class=\"code-line\">記事のスタイリングは整えるのが正直すごい面倒で、過去に完璧を目指して挫折したことがある鬼門。\x3Cbr />
2\nいつも仕事でお世話になっているzennさんが公開しているmarkdownパーサーライブラリをそのまま使わせていただきました。\x3Cbr />\nリンクをブログカード的な表示にしてくれたりめちゃめちゃ嬉しい...\x3Cbr />\n(ただ、リンク先のOGP情報を取得するエンドポイントはzennさんが用意されているものにタダ乗りしてしまっていて、自分の管理下にないのが少し気になる)\x3Cbr />\n適用はこちらの記事をそのまま行いました。\x3C/p>\n\x3Cp data-line=\"40\" class=\"code-line\">\x3Ca href=\"https://zenn.dev/waddy/articles/intro-zenn-markdown\" rel=\"noreferrer noopener nofollow\" target=\"_blank\">https://zenn.dev/waddy/articles/intro-zenn-markdown\x3C/a>\x3Ca href=\"https://zenn.dev/waddy/articles/intro-zenn-markdown\" style=\"display:none\" target=\"_blank\">https://zenn.dev/waddy/articles/intro-zenn-markdown\x3C/a>\x3C/p>\n\x3Ch3 id=\"front-matter%E9%A0%85%E7%9B%AE%E4%BF%AE%E6%AD%A3\" data-line=\"42\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#front-matter%E9%A0%85%E7%9B%AE%E4%BF%AE%E6%AD%A3\" aria-hidden=\"true\">\x3C/a> front-matter項目修正\x3C/h3>\n\x3Cp data-line=\"44\" class=\"code-line\">Next.jsの \x3Ccode>blog-starter-typescript\x3C/code> で構築した場合、記事のデフォルトのfront-matterには個人ブログでは不要なものや設定が面倒な項目があったã
2®ã§è‡ªåˆ†ãŒæ›¸ãã‚„すいようにカスタマイズし、最終的には下記のようになりました。\x3C/p>\n\x3Cdiv class=\"code-block-container\">\x3Cpre class=\"shiki github-dark\" style=\"background-color:#151e2c;color:#e1e4e8\">\x3Ccode class=\"code-line\" data-line=\"46\">\x3Cspan class=\"line\">\x3Cspan>---\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan>title: \"面倒くさがりがNext.jsでブログを構築してみた\"\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan>coverImage: ''\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan>date: '2022-04-02T07:04:11.054Z'\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan>tags:\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan>  - 'Next.js'\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan>  - 'React'\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan>---\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan>\x3C/span>\x3C/span>\x3C/code>\x3C/pre>\x3C/div>\x3Cp data-line=\"57\" class=\"code-line\">一覧画面に表示される記事の説明文章を入れる項目である\x3Ccode>excerpt\x3C/code>は、本文のinnerTextを先頭500文字分切り出すようにしました。\x3C/p>\n\x3Cp data-line=\"59\" class=\"code-line\">\x3Cimg src=\"/assets/posts/start-my-nextjs-blog/2022-04-03-13-53-15.png\" alt=\"\" class=\"md-img\" loading=\"lazy\" />\x3Cbr />\n\x3Cem>タイトル下の文字が\x3Ccode>excerpt\x3C/code>\x3C/em>\x3C/p>\n\x3Cp data-line=\"62\" class=\"code-line\">記事本文のmarkdownからHTMLへの変換処理は、デフォルトでは記事のコンポーネント内で処理していましたが、記事側でmarkdownのまま文字列を操作したくなることは今後ないと思われるため、\x3Ccode>lib/api.ts\x3C/code>内に移動させました。そして変換されたHTML文字列を\x3Ccode>node-html-parser\x3C/code>でparseし、本文のinnerTextを先頭から500文字切り出したものを\x3Ccode>excerpt\x3C/code>として表示するようにしています。\x3C/p>\n\x3Ch3 id=\"%E8%A8%98%E4%BA%8B%E3%81%B8%E3%81%AE%E7%94%BB%E5%83%8F%E3%81%AE%E6%8C%BF%E5%85%A5\" data-line=\"64\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E8%A8%98%E4%BA%8B%E3%81%B8%E3%81%AE%E7%94%BB%E5%83%8F%E3%81%AE%E6%8C%BF%E5%85%A5\" aria-hidden=\"true\">\x3C/a> 記事への画像の挿入\x3C/h3>\n\x3Cp data-line=\"66\" class=\"code-line\">ショートカットコマンドを使った画像の挿入はvscodeのPasteImageプラグインを使えば簡単に実現できました。\x3Cbr />\n\x3Ccode>blog-starter-typescript\x3C/code> での記事の保存場所は \x3Ccode>public/assets/blog/{記事slug}/{画像名.jpg}\x3C/code>
2で保存されているので、\x3Ccode>option + command + V\x3C/code> で画像をそちらに保存するように下記のように設定しました。\x3C/p>\n\x3Cdiv class=\"code-block-container\">\x3Cdiv class=\"code-block-filename-container\">\x3Cspan class=\"code-block-filename\">.vscode/settings.json\x3C/span>\x3C/div>\x3Cpre class=\"shiki github-dark\" style=\"background-color:#151e2c;color:#e1e4e8\">\x3Ccode class=\"code-line\" data-line=\"69\">\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">{\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">  \"pasteImage.path\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"${projectRoot}/public/assets/blog/${currentFileNameWithoutExt}\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">  \"pasteImage.basePath\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"${projectRoot}/public\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">  \"pasteImage.forceUnixStyleSeparator\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">: \x3C/span>\x3Cspan style=\"color:#79B8FF\">true\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">  \"pasteImage.prefix\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"/\"\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">}\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\x3C/code>\x3C/pre>\x3C/div>\x3Ch3 id=\"%E6%96%B0%E8%A6%8F%E8%A8%98%E4%BA%8B%E8%BF%BD%E5%8A%A0%E3%81%AE%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E5%87%A6%E7%90%86%E5%AE%9F%E8%A3%85\" data-line=\"78\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E6%96%B0%E8%A6%8F%E8%A8%98%E4%BA%8B%E8%BF%BD%E5%8A%A0%E3%81%AE%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E5%87%A6%E7%90%86%E5%AE%9F%E8%A3%85\" aria-hidden=\"true\">\x3C/a> 新規記事追加のコマンド処理実装\x3C/h3>\n\x3Cp data-line=\"80\" class=\"code-line\">\x3Ccode>yarn new:post {slug}\x3C/code> で記事のファイルを作成したいので、実装してみた。\x3C/p>\n\x3Cp data-line=\"82\" class=\"code-line\">新規記事作成コマンドの内容の処理を \x3Ccode>commands/new-post.js\x3C/code> に実装した。\x3C/p>\n\x3Cdiv class=\"code-block-container\">\x3Cdiv class=\"code-block-filename-container\">\x3Cspan class=\"code-block-filename\">commands/new-post.js\x3C/span>\x3C/div>\x3Cpre class=\"shiki github-dark\" style=\"background-color:#151e2c;color:#e1e4e8\">\x3Ccode class=\"code-line\" data-line=\"84\">\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> fs\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#B392F0\"> require\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3Cspan style=\"color:#9ECBFF\">'fs-extra'\x3C/span>\x3Cspan style=\"color:#E1E4E8\">)\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> arg\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#B392F0\"> require\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3Cspan style=\"color:#9ECBFF\">'arg'\x3C/span>\x3Cspan style=\"color:#E1E4E8\">)\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> path\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#B392F0\"> require\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3Cspan style=\"color:#9ECBFF\">'path'\x3C/span>\x3Cspan style=\"color:#E1E4E8\">)\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">function\x3C/span>\x3Cspan style=\"color:#B392F0\"> getWorkingPath\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3Cspan style=\"color:#FFAB70\">
2pathFromWorkingDir\x3C/span>\x3Cspan style=\"color:#E1E4E8\">) {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">  if\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> (\x3C/span>\x3Cspan style=\"color:#9ECBFF\">/\x3C/span>\x3Cspan style=\"color:#85E89D;font-weight:bold\">\\.\\.\x3C/span>\x3Cspan style=\"color:#9ECBFF\">/\x3C/span>\x3Cspan style=\"color:#E1E4E8\">.\x3C/span>\x3Cspan style=\"color:#B392F0\">test\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(pathFromWorkingDir)) {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">    console.\x3C/span>\x3Cspan style=\"color:#B392F0\">error\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">      '取得するファイル/ディレクトリの名前に不正な文字列が含まれているため処理を終了します'\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">    );\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">    process.\x3C/span>\x3Cspan style=\"color:#B392F0\">exit\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3Cspan style=\"color:#79B8FF\">1\x3C/span>\x3Cspan style=\"color:#E1E4E8\">);\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  }\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">  return\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> path.\x3C/span>\x3Cspan style=\"color:#B392F0\">join\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(process.\x3C/span>\x3Cspan style=\"color:#B392F0\">cwd\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(), pathFromWorkingDir.\x3C/span>\x3Cspan style=\"color:#B392F0\">replace\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3Cspan style=\"color:#9ECBFF\">/\x3C/span>\x3Cspan style=\"color:#F97583\">^\x3C/span>\x3Cspan style=\"color:#85E89D;font-weight:bold\">\\/\x3C/span>\x3Cspan style=\"color:#9ECBFF\">/\x3C/span>\x3Cspan style=\"color:#E1E4E8\">, \x3C/span>\x3Cspan style=\"color:#9ECBFF\">''\x3C/span>\x3Cspan style=\"color:#E1E4E8\">));\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">}\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">function\x3C/span>\x3Cspan style=\"color:#B392F0\"> generateFileIfNotExist\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3Cspan style=\"color:#FFAB70\">fullpath\x3C/span>\x3Cspan style=\"color:#E1E4E8\">, \x3C/span>\x3Cspan style=\"color:#FFAB70\">content\x3C/span>\x3Cspan style=\"color:#E1E4E8\">) {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  fs.\x3C/span>\x3Cspan style=\"color:#B392F0\">outputFileSync\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">    fullpath,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">    content,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">    { flag: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">'wx'\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> }\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  );\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">}\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> args\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#B392F0\"> arg\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  {},\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">    permissive: \x3C/span>\x3Cspan style=\"color:#79B8FF\">true\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  }\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">);\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">
2const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> slug\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> args._[\x3C/span>\x3Cspan style=\"color:#79B8FF\">0\x3C/span>\x3Cspan style=\"color:#E1E4E8\">]\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> fileName\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> `${\x3C/span>\x3Cspan style=\"color:#E1E4E8\">slug\x3C/span>\x3Cspan style=\"color:#9ECBFF\">}.md`\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> relativeFilePath\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> `_posts/${\x3C/span>\x3Cspan style=\"color:#E1E4E8\">fileName\x3C/span>\x3Cspan style=\"color:#9ECBFF\">}`\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> fullFilepath\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#B392F0\"> getWorkingPath\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(relativeFilePath)\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> today\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3Cspan style=\"color:#F97583\"> new\x3C/span>\x3Cspan style=\"color:#B392F0\"> Date\x3C/span>\x3Cspan style=\"color:#E1E4E8\">()\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">const\x3C/span>\x3Cspan style=\"color:#79B8FF\"> fileBody\x3C/span>\x3Cspan style=\"color:#F97583\"> =\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  [\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">    '---'\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">    `title: \"\"`\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">    `coverImage: ''`\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">    `date: '${\x3C/span>\x3Cspan style=\"color:#E1E4E8\">today\x3C/span>\x3Cspan style=\"color:#9ECBFF\">.\x3C/span>\x3Cspan style=\"color:#B392F0\">toISOString\x3C/span>\x3Cspan style=\"color:#9ECBFF\">()\x3C/span>\x3Cspan style=\"color:#9ECBFF\">}'`\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">    `ogImage: ''`\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">    'tags:'\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">    `  - 'example'`\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">    '---'\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  ].\x3C/span>\x3Cspan style=\"color:#B392F0\">join\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3Cspan style=\"color:#9ECBFF\">'\x3C/span>\x3Cspan style=\"color:#79B8FF\">\\n\x3C/span>\x3Cspan style=\"color:#9ECBFF\">'\x3C/span>\x3Cspan style=\"color:#E1E4E8\">) \x3C/span>\x3Cspan style=\"color:#F97583\">+\x3C/span>\x3Cspan style=\"color:#9ECBFF\"> '\x3C/span>\x3Cspan style=\"color:#79B8FF\">\\n\x3C/span>\x3Cspan style=\"color:#9ECBFF\">'\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">try\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#B392F0\">  generateFileIfNotExist\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(fullFilepath, fileBody);\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  console.\x3C/span>\x3Cspan style=\"color:#B392F0\">
2log\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(relativeFilePath);\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">} \x3C/span>\x3Cspan style=\"color:#F97583\">catch\x3C/span>\x3Cspan style=\"color:#E1E4E8\"> (err) {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  console.\x3C/span>\x3Cspan style=\"color:#B392F0\">error\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(\x3C/span>\x3Cspan style=\"color:#9ECBFF\">'記事のファイル作成時にエラーが発生しました'\x3C/span>\x3Cspan style=\"color:#E1E4E8\">);\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  console.\x3C/span>\x3Cspan style=\"color:#B392F0\">error\x3C/span>\x3Cspan style=\"color:#E1E4E8\">(err);\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">}\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\x3C/code>\x3C/pre>\x3C/div>\x3Cp data-line=\"141\" class=\"code-line\">実装した処理を \x3Ccode>yarn\x3C/code> 側で呼び出せるように \x3Ccode>scripts\x3C/code> に追加した。\x3C/p>\n\x3Cdiv class=\"code-block-container\">\x3Cdiv class=\"code-block-filename-container\">\x3Cspan class=\"code-block-filename\">package.json\x3C/span>\x3C/div>\x3Cpre class=\"shiki github-dark\" style=\"background-color:#151e2c;color:#e1e4e8\">\x3Ccode class=\"code-line\" data-line=\"143\">\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">{\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">  \"private\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">: \x3C/span>\x3Cspan style=\"color:#79B8FF\">true\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">  \"scripts\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">
2: {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">    \"dev\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"next\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">    \"build\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"next build\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">    \"start\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"next start\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">    \"typecheck\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"tsc\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">,\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">+    \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"new:post\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">: \x3C/span>\x3Cspan style=\"color:#9ECBFF\">\"node commands/new-post.js\"\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  },\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#9ECBFF\">  \"dependencies\"\x3C/span>\x3Cspan style=\"color:#E1E4E8\">
2: {\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">  ...\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">  },\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#F97583\">  ...\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3Cspan style=\"color:#E1E4E8\">}\x3C/span>\x3C/span>\n\x3Cspan class=\"line\">\x3C/span>\x3C/code>\x3C/pre>\x3C/div>\x3Ch2 id=\"%E4%BB%8A%E5%BE%8C%E6%AC%B2%E3%81%97%E3%81%84%E6%A9%9F%E8%83%BD\" data-line=\"160\" class=\"code-line\">\x3Ca class=\"header-anchor-link\" href=\"#%E4%BB%8A%E5%BE%8C%E6%AC%B2%E3%81%97%E3%81%84%E6%A9%9F%E8%83%BD\" aria-hidden=\"true\">\x3C/a> 今後欲しい機能\x3C/h2>\n\x3Cp data-line=\"162\" class=\"code-line\">現在は記事数が少ないので問題ないのですが、将来的には下記のような記事を検索する機能を作るとより便利になりそう。\x3C/p>\n\x3Cul data-line=\"164\" class=\"code-line\">\n\x3Cli data-line=\"164\" class=\"code-line\">ページネーション機能\x3C/li>\n\x3Cli data-line=\"165\" class=\"code-line\">タグ検索機能\x3C/li>\n\x3Cli data-line=\"166\" class=\"code-line\">全文検索機能\x3C/li>\n\x3C/ul>\n",tags:$R[46]=["Next.js","React"],date:"2022-04-02T07:04:11.054Z"}],ssr:!0}],lastMatchId:"�posts��posts�"})($R["tsr"]);$_TSR.e();document.currentScript.remove()</script>
2<script type="module" async="">import("/assets/main-BTvl8QpF.js")</script>
2</body></html>

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.