1<!DOCTYPE html><html lang="ja"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width, initial-scale=1"/><link rel="stylesheet" href="/assets/styles-D6MIEP-t.css" data-precedence="default"/><link rel="stylesheet" href="/assets/index-DtxX27Jz.css" type="text/css" data-precedence="default"/><title>å人ãµã¤ãããªãªã¸ã³ãµã¼ãã¼ãªãã§éç¨ãã â Cloudflare Workers SSR ã®æ§æã¨ãããã©ãã â Kensuke Kubo</title><meta name="theme-color" content="#efe9dc"/><meta description="ããã³ãã¨ã³ãããã¤ã³ãã©ã¾ã§å¹
åºã対å¿ãããããã¯ãã¨ã³ã¸ãã¢ãè¨è¨ã»å®è£
ã®ç¥è¦ãè¨äºã¨ãã¦çºä¿¡ãã¦ãã¾ãã"/><meta property="og:site_name" content="Kensuke Kubo"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:url" content="https://ken109.dev"/><meta name="description" content="TanStack Start ã Cloudflare Workers ã§ SSR ããå¤é¨ API 飿ºãã¨ãã¸ãã£ãã·ã¥ã«æããæ§æã®å®è£
ãfetch ã® cf ãªãã·ã§ã³ãConnectRPC ã GET ã§å©ãæ¹æ³ãå®éã«ããã£ãç½ ã解説ããã"/><meta property="og:type" content="article"/><meta property="og:title" content="å人ãµã¤ãããªãªã¸ã³ãµã¼ãã¼ãªãã§éç¨ãã â Cloudflare Workers SSR ã®æ§æã¨ãããã©ãã"/><meta property="og:description" content="TanStack Start ã Cloudflare Workers ã§ SSR ããå¤é¨ API 飿ºãã¨ãã¸ãã£ãã·ã¥ã«æããæ§æã®å®è£
ãfetch ã® cf ãªãã·ã§ã³ãConnectRPC ã GET ã§å©ãæ¹æ³ãå®éã«ããã£ãç½ ã解説ããã"/><meta property="og:image" content="https://ken109.dev/og/articles/edge-first-architecture"/><meta property="og:url" content="https://ken109.dev/articles/edge-first-architecture"/><meta property="article:published_time" content="2026-06-04"/><meta property="article:author" content="Kensuke Kubo"/><meta property="article:tag" content="ConnectRPC"/><meta property="twitter:title" content="å人ãµã¤ãããªãªã¸ã³ãµã¼ãã¼ãªãã§éç¨ãã â Cloudflare Workers SSR ã®æ§æã¨ãããã©ãã"/><meta property="twitter:description" content="TanStack Start ã Cloudflare Workers ã§ SSR ããå¤é¨ API 飿ºãã¨ãã¸ãã£ãã·ã¥ã«æããæ§æã®å®è£
ãfetch ã® cf ãªãã·ã§ã³ãConnectRPC ã GET ã§å©ãæ¹æ³ãå®éã«ããã£ãç½ ã解説ããã"/><meta property="twitter:image" content="https://ken109.dev/og/articles/edge-first-architecture"/><link rel="modulepreload" href="/assets/index-ClIfQcG2.js"/><link rel="modulepreload" href="/assets/articles._slug-x3piR9Zp.js"/><link rel="modulepreload" href="/assets/article-list-B3ruWv1x.js"/><link rel="icon" href="/favicon.ico" sizes="48x48 32x32 16x16"/><link rel="icon" href="/icon.png" type="image/png" sizes="512x512"/><link rel="apple-touch-icon" href="/apple-touch-icon.png" sizes="180x180"/><link rel="manifest" href="/manifest.json"/><link rel="sitemap" type="application/xml" href="https://ken109.dev/sitemap.xml"/><link rel="canonical" href="https://ken109.dev/articles/edge-first-architecture"/>
1<script type="application/ld+json">{"@context":"https://schema.org","@type":"Person","name":"Kensuke Kubo","alternateName":"ken109","url":"https://ken109.dev","jobTitle":"Product Engineer","sameAs":["https://github.com/ken109","https://x.com/ken109_","https://zenn.dev/ken109","https://dev.to/ken109"]}</script>
1<script type="application/ld+json">{"@context":"https://schema.org","@type":"WebSite","name":"Kensuke Kubo","description":"ããã³ãã¨ã³ãããã¤ã³ãã©ã¾ã§å¹
åºã対å¿ãããããã¯ãã¨ã³ã¸ãã¢ãè¨è¨ã»å®è£
ã®ç¥è¦ãè¨äºã¨ãã¦çºä¿¡ãã¦ãã¾ãã","url":"https://ken109.dev","inLanguage":"ja","author":{"@type":"Person","name":"Kensuke Kubo","url":"https://ken109.dev"}}</script>
1<script type="application/ld+json">{"@context":"https://schema.org","@type":"BlogPosting","headline":"å人ãµã¤ãããªãªã¸ã³ãµã¼ãã¼ãªãã§éç¨ãã â Cloudflare Workers SSR ã®æ§æã¨ãããã©ãã","description":"TanStack Start ã Cloudflare Workers ã§ SSR ããå¤é¨ API 飿ºãã¨ãã¸ãã£ãã·ã¥ã«æããæ§æã®å®è£
ãfetch ã® cf ãªãã·ã§ã³ãConnectRPC ã GET ã§å©ãæ¹æ³ãå®éã«ããã£ãç½ ã解説ããã","datePublished":"2026-06-04","keywords":"Cloudflare Workers,TanStack Start,ConnectRPC","image":"https://ken109.dev/og/articles/edge-first-architecture","inLanguage":"ja","mainEntityOfPage":{"@type":"WebPage","@id":"https://ken109.dev/articles/edge-first-architecture"},"author":{"@type":"Person","name":"Kensuke Kubo","url":"https://ken109.dev"}}</script>
1<script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https://ken109.dev"},{"@type":"ListItem","position":2,"name":"Articles","item":"https://ken109.dev/articles/"},{"@type":"ListItem","position":3,"name":"å人ãµã¤ãããªãªã¸ã³ãµã¼ãã¼ãªãã§éç¨ãã â Cloudflare Workers SSR ã®æ§æã¨ãããã©ãã","item":"https://ken109.dev/articles/edge-first-architecture"}]}</script>
1</head><body><div class="min-h-dvh flex flex-col"><div aria-hidden="true"><div class="paper-sheet"></div><div class="paper-grain"></div><i class="reg reg-tl"></i><i class="reg reg-tr"></i><i class="reg reg-bl"></i><i class="reg reg-br"></i></div><header class="relative z-10"><div class="relative mx-auto max-w-[1440px] px-(--gutter)"><div class="flex flex-col gap-2 pt-5 pb-3.5 border-b border-rule sm:flex-row sm:items-baseline sm:justify-between sm:gap-5"><a tabindex="0" href="/" class="text-sm transition-all decoration-1 hover:decoration-2 decoration-border underline-offset-4 flex flex-wrap gap-x-4 caption font-normal no-underline text-ink-700 hover:text-klein"><span class="hidden sm:inline">Specimen Sheet Nº 109</span><span>ken109.dev</span><time>2026-10-06</time></a><nav aria-label="ã¡ã¤ã³ããã²ã¼ã·ã§ã³"><ol class="flex items-baseline justify-between gap-3 sm:gap-6"><li><a tabindex="0" href="/#journey" class="transition-all decoration-1 hover:decoration-2 decoration-border underline-offset-4 group font-mono text-[11px] font-medium uppercase tracking-[0.06em] no-underline text-foreground hover:text-klein"><span class="mr-1.5 hidden text-klein sm:inline">01</span>Journey</a></li><li><a tabindex="0" href="/#articles" class="transition-all decoration-1 hover:decoration-2 decoration-border underline-offset-4 group font-mono text-[11px] font-medium uppercase tracking-[0.06em] no-underline text-foreground hover:text-klein"><span class="mr-1.5 hidden text-klein sm:inline">02</span>Articles</a></li><li><a tabindex="0" href="/#now" class="transition-all decoration-1 hover:decoration-2 decoration-border underline-offset-4 group font-mono text-[11px] font-medium uppercase tracking-[0.06em] no-underline text-foreground hover:text-klein"><span class="mr-1.5 hidden text-klein sm:inline">03</span>Now</a></li><li><a tabindex="0" href="/#contact" class="transition-all decoration-1 hover:decoration-2 decoration-border underline-offset-4 group font-mono text-[11px] font-medium uppercase tracking-[0.06em] no-underline text-foreground hover:text-klein"><span class="mr-1.5 hidden text-klein sm:inline">04</span>Contact</a></li></ol></nav></div></div></header><main class="flex-1 w-full flex flex-col"><!--$--><!--$--><div class="relative mx-auto max-w-[1440px] px-(--gutter) py-16 sm:py-24"><article class="grid grid-cols-12 gap-x-6"><header class="col-span-12 lg:col-span-3 mb-12 lg:mb-0 rise" style="--rise-index:0"><div class="lg:sticky lg:top-24 space-y-8 font-mono text-xs"><div><div class="caption text-muted-foreground mb-1.5">Published</div><div class="text-foreground">2026.06.04</div></div><div><div class="caption text-muted-foreground mb-1.5">Views</div><div>91</div></div></div></header><div class="col-span-12 lg:col-span-9 lg:border-l lg:border-rule lg:pl-10 rise" style="--rise-index:1"><h1 class="text-2xl sm:text-4xl font-bold tracking-tight leading-snug">å人ãµã¤ãããªãªã¸ã³ãµã¼ãã¼ãªãã§éç¨ãã â Cloudflare Workers SSR ã®æ§æã¨ãããã©ãã</h1><div class="mt-5 mb-10 flex flex-wrap items-center gap-2"><span class="border border-rule px-2.5 py-1 font-mono text-[10px] tracking-[0.15em] uppercase text-foreground/70">Cloudflare Workers</span><span class="border border-rule px-2.5 py-1 font-mono text-[10px] tracking-[0.15em] uppercase text-foreground/70">TanStack Start</span><span class="border border-rule px-2.5 py-1 font-mono text-[10px] tracking-[0.15em] uppercase text-foreground/70">ConnectRPC</span></div><div><p class="text-[15px] sm:text-base leading-8 text-foreground/90 mb-10">ãã®ãµã¤ãã¯ãªãªã¸ã³ãµã¼ãã¼ãæããªããSSR ãå¤é¨ API 飿ºã Cloudflare Workers ã ãã§å®çµããéç¨ã³ã¹ãã¯ã»ã¼ã¼ããéµã¯<mark class="bg-foreground
1/8 text-inherit px-1 py-0.5 box-decoration-clone">ãå¤é¨ API ã¸ã® fetch ããã¹ã¦ã¨ãã¸ãã£ãã·ã¥ã«æããã</mark>è¨è¨ã§ã ãã®å®è£
ã¨å®éã«ããã£ãç½ ããã®è¨äºã«ã¾ã¨ããã</p><ul class="my-5 space-y-2"><li class="text-[15px] leading-7 text-foreground/85 pl-5 relative before:content-['â'] before:absolute before:left-0 before:text-muted-foreground"><strong>対象èªè
</strong>: å人ãµã¤ããå°è¦æ¨¡ãµã¼ãã¹ã Cloudflare Workers ã§ SSR éç¨ããã人ãReact 㨠fetch API ã®åºæ¬ãåæã¨ãã</li><li class="text-[15px] leading-7 text-foreground/85 pl-5 relative before:content-['â'] before:absolute before:left-0 before:text-muted-foreground"><strong>ç°å¢</strong>: TanStack Start 1.167 / @cloudflare/vite-plugin 1.39 / wrangler 4.95</li><li class="text-[15px] leading-7 text-foreground/85 pl-5 relative before:content-['â'] before:absolute before:left-0 before:text-muted-foreground"><strong>ããããã¨</strong>: å¤é¨ API ãã¨ãã¸ãã£ãã·ã¥ã§æããå®è£
ãã¿ã¼ã³ãConnectRPC ã CF ãã£ãã·ã¥ã«ä¹ããæ¹æ³ãSSR ãã³ãã«ã§å®éã«è¸ãã ç½ </li><li class="text-[15px] leading-7 text-foreground/85 pl-5 relative before:content-['â'] before:absolute before:left-0 before:text-muted-foreground"><strong>æ±ããªããã¨</strong>: Workers ã®èª²éä½ç³»ãD1/KV ã使ã£ãæ°¸ç¶å</li></ul><h2 id="section-01" data-toc-index="01" class="mt-14 mb-6 pt-6 pb-4 border-t border-b border-rule flex items-baseline gap-4 scroll-mt-24"><span class="font-mono text-xs text-muted-foreground">01</span><span data-toc-title="true" class="text-xl sm:text-2xl font-bold tracking-tight">çµè«: ãªã¯ã¨ã¹ãã¯ã¨ãã¸ã§å®çµããã</span></h2><p class="text-[15px] leading-8 text-foreground/85 my-5">æ§æã®è¦ç¹ã¯3ã¤ã<strong>â SSR 㯠Workerãéçã¢ã»ãã㯠Workers Static Assets</strong>(Worker ã®èµ·åããçºçããªã)ã<strong>â¡ å¤é¨ API(Zenn / GitHub / Bluesky / èªåã® ConnectRPC)ã¯ãã¹ã¦ SSR ã® loader ãã fetch ããã¨ãã¸ã« 1ã24 æéãã£ãã·ã¥</strong>ã<strong>⢠ã©ã®é£æºãè½ã¡ã¦ãã»ã¯ã·ã§ã³ãæ¶ããã ã</strong>ã§ãã¼ã¸ã¯å£ããªããDB ã cron ãæããªãããã<span class="underline decoration-klein/70 decoration-2 underline-offset-[5px]">é害ç¹ã¯å®è³ª Worker 1 ã¤</span>ã«ãªãã</p><figure class="my-8 border border-rule p-5 sm:p-6"><div><div class="group relative"><div class="w-full font-mono text-(--dg-ink) transition-opacity opacity-0" style="--dg-ink:var(--diagram-ink, var(--foreground));--dg-stroke:var(--diagram-stroke, var(--foreground));--dg-fill:var(--diagram-fill, var(--background));--dg-inverse-ink:var(--diagram-inverse-ink, var(--background));--dg-inverse-fill:var(--diagram-inverse-fill, var(--foreground));
1--dg-accent:var(--diagram-accent, var(--destructive));--dg-accent-ink:var(--diagram-accent-ink, var(--destructive-foreground));--dg-muted-ink:var(--diagram-muted-ink, var(--muted-foreground));--dg-muted-fill:var(--diagram-muted-fill, var(--muted));--dg-muted-stroke:var(--diagram-muted-stroke, var(--muted-foreground));--dg-line:var(--diagram-line, var(--border));height:320px"><div data-testid="rf__wrapper" style="width:100%;height:100%;overflow:hidden;position:relative;z-index:0" class="react-flow light" role="application"><div class="react-flow__renderer" style="position:absolute;width:100%;height:100%;top:0;left:0"><div class="react-flow__pane" style="position:absolute;width:100%;height:100%;top:0;left:0"><div class="react-flow__viewport xyflow__viewport react-flow__container" style="transform:translate(0px,0px) scale(1)"><div class="react-flow__edges"></div><div class="react-flow__edgelabel-renderer"></div><div class="react-flow__nodes" style="position:absolute;width:100%;height:100%;top:0;left:0"></div><div class="react-flow__viewport-portal"></div></div></div></div><div id="react-flow__node-desc-1" style="display:none">Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.</div><div id="react-flow__edge-desc-1" style="display:none">Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.</div><div id="react-flow__aria-live-1" aria-live="assertive" aria-atomic="true" style="position:absolute;width:1px;height:1px;margin:-1px;border:0;padding:0;overflow:hidden;clip:rect(0px, 0px, 0px, 0px);clip-path:inset(100%)"></div></div></div><button aria-label="å³ãå
¨ç»é¢ã§éã" class="absolute top-2 right-2 z-10 flex size-7 cursor-zoom-in items-center justify-center border border-(--dg-stroke) bg-(--dg-fill) text-(--dg-ink) opacity-50 transition-opacity hover:opacity-100 group-hover:opacity-80" style="--dg-ink:var(--diagram-ink, var(--foreground));--dg-stroke:var(--diagram-stroke, var(--foreground));--dg-fill:var(--diagram-fill, var(--background));--dg-inverse-ink:var(--diagram-inverse-ink, var(--background));--dg-inverse-fill:var(--diagram-inverse-fill, var(--foreground));
1--dg-accent:var(--diagram-accent, var(--destructive));--dg-accent-ink:var(--diagram-accent-ink, var(--destructive-foreground));--dg-muted-ink:var(--diagram-muted-ink, var(--muted-foreground));--dg-muted-fill:var(--diagram-muted-fill, var(--muted));--dg-muted-stroke:var(--diagram-muted-stroke, var(--muted-foreground));--dg-line:var(--diagram-line, var(--border))" type="button"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 size-3.5" aria-hidden="true"><path d="M15 3h6v6"></path><path d="m21 3-7 7"></path><path d="m3 21 7-7"></path><path d="M9 21H3v-6"></path></svg></button></div></div><figcaption class="mt-4 border-t border-border pt-2.5 font-mono text-[10px] tracking-[0.1em] text-muted-foreground uppercase">Fig. 1 â ãªã¯ã¨ã¹ãã®æµããå¤é¨ API 㯠loader ãã fetch ãã¨ãã¸ã«ä¹ãã</figcaption></figure><h2 id="section-02" data-toc-index="02" class="mt-14 mb-6 pt-6 pb-4 border-t border-b border-rule flex items-baseline gap-4 scroll-mt-24"><span class="font-mono text-xs text-muted-foreground">02</span><span data-toc-title="true" class="text-xl sm:text-2xl font-bold tracking-tight">å¤é¨ API 㯠fetch ã® cf ãªãã·ã§ã³ã§æãã</span></h2><p class="text-[15px] leading-8 text-foreground/85 my-5">
1çµè«ããè¨ãã¨ã<mark class="bg-foreground/8 text-inherit px-1 py-0.5 box-decoration-clone">èªåã®ãã£ãã·ã¥å±¤ãæ¸ãå¿ è¦ã¯ãªã</mark>ãWorkers ã® <code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">fetch</code> ã¯<code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">cf</code> <!-- -->ãªãã·ã§ã³ã渡ãã ãã§ã¬ã¹ãã³ã¹ãã¨ãã¸ãã£ãã·ã¥ã«ä¹ããããããã®ãµã¤ãã®å ¨é£æºã¯æ¬¡ã® 1 ãã¿ã¼ã³ã§å®è£ ãã¦ããã</p><figure class="group relative overflow-hidden border my-6 rounded-none border-rule bg-transparent"><figcaption class="flex items-center justify-between gap-2 pl-4 pr-2 py-1.5 border-b border-border font-mono text-[10px] tracking-wider text-muted-foreground">features/articles/lib/external-articles.ts (æç²)<button data-slot="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xs text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive hover:bg-accent cursor-pointer size-7 text-muted-foreground hover:text-foreground" type="button" aria-label="ã³ã¼ããã³ãã¼"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy size-3.5" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></figcaption><pre class="p-4 overflow-x-auto"><code class="hljs font-mono text-[13px] leading-6 bg-transparent"><span class="hljs-keyword">const</span> <span class="hljs-variable constant_">FETCH_INIT</span> = { 2 <span class="hljs-attr">headers</span>: { <span class="hljs-string">"User-Agent"</span>: <span class="hljs-string">"ken109.dev (https://ken109.dev)"</span> }, 3 <span class="hljs-attr">cf</span>: { <span class="hljs-attr">cacheTtl</span>: <span class="hljs-number">3600</span>, <span class="hljs-attr">cacheEverything</span>: <span class="hljs-literal">true</span> } 4} <span class="hljs-keyword">as</span> <span class="hljs-title class_">RequestInit</span>; 5 6<span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(<span class="hljs-variable constant_">ZENN_API</span>, <span class="hljs-variable constant_">FETCH_INIT</span>);</code></pre></figure><p class="text-[15px] leading-8 text-foreground/85 my-5">æåã¯ã¢ã¸ã¥ã¼ã«ã¹ã³ã¼ãã®å¤æ°ã« TTL ä»ãã§æã¤ãã¡ã¢ãªãã£ãã·ã¥ããæ¸ããããããæ¨ã¦ããæ¯è¼ããã¨:</p><div dir="ltr" data-slot="scroll-area" class="flex flex-col relative overflow-hidden my-6" style="position:relative;--radix-scroll-area-corner-width:0px;--radix-scroll-area-corner-height:0px"><style>[data-radix-scroll-area-viewport]{scrollbar-width:none;-ms-overflow-style:none;-webkit-overflow-scrolling:touch;}[data-radix-scroll-area-viewport]::-webkit-scrollbar{display:none}</style><div style="overflow-x:hidden;overflow-y:hidden;min-width:100%;display:table" data-radix-scroll-area-viewport="" data-slot="scroll-area-viewport" class="focus-visible:ring-ring/50 rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1 flex-grow w-full !block"><div class=""><table class="w-full border border-rule text-sm"><thead><tr class="bg-foreground"><th class="px-4 py-2.5 text-left caption text-background/90 font-bold">æ¹å¼</th><th class="px-4 py-2.5 text-left caption text-background/90 font-bold">çåæé</th><th class="px-4 py-2.5 text-left caption text-background/90 font-bold">ã³ã¹ã</th><th class="px-4 py-2.5 text-left caption text-background/90 font-bold">åè</th></tr></thead><tbody><tr class="border-b border-rule last:border-b-0 even:bg-foreground
6/3"><td class="px-4 py-2.5 align-top leading-6">ã¢ã¸ã¥ã¼ã«ã¹ã³ã¼ã夿°</td><td class="px-4 py-2.5 align-top leading-6">isolate ãçãã¦ããéã ã</td><td class="px-4 py-2.5 align-top leading-6">ç¡æ</td><td class="px-4 py-2.5 align-top leading-6">ãã¤æ¶ãããä¿è¨¼ããªããcolo æ¯ã»isolate æ¯ã«ãã©ãã©</td></tr><tr class="border-b border-rule last:border-b-0 even:bg-foreground/3"><td class="px-4 py-2.5 align-top leading-6">cf ãªãã·ã§ã³ (æ¡ç¨)</td><td class="px-4 py-2.5 align-top leading-6">TTL ã¾ã§ (colo åä½)</td><td class="px-4 py-2.5 align-top leading-6">ç¡æ</td><td class="px-4 py-2.5 align-top leading-6">ã³ã¼ã 1 è¡ã§æ¸ããGET / HEAD ã®ã¿å¯¾è±¡</td></tr><tr class="border-b border-rule last:border-b-0 even:bg-foreground/3"><td class="px-4 py-2.5 align-top leading-6">Workers KV</td><td class="px-4 py-2.5 align-top leading-6">æç¤ºçã«æ¶ãã¾ã§</td><td class="px-4 py-2.5 align-top leading-6">ç¡ææ ãã</td><td class="px-4 py-2.5 align-top leading-6">colo ãè·¨ãã§å
±æãããå ´åã¯ãã¡ã</td></tr></tbody></table></div></div></div><p class="text-[15px] leading-8 text-foreground/85 my-5">注æç¹ã¯3ã¤ã</p><ul class="my-5 space-y-2"><li class="text-[15px] leading-7 text-foreground/85 pl-5 relative before:content-['â'] before:absolute before:left-0 before:text-muted-foreground"><code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">cf.cacheTtl</code> ãå¹ãã®ã¯ <strong>GET ãªã¯ã¨ã¹ãã®ã¿</strong>ãPOST ã¯ç´ éããã</li><li class="text-[15px] leading-7 text-foreground/85 pl-5 relative before:content-['â'] before:absolute before:left-0 before:text-muted-foreground">JSON API ã®ã¬ã¹ãã³ã¹ã¯æ¢å®ã§ã¯ãã£ãã·ã¥å¯¾è±¡å¤ã®ãã <code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">cacheEverything: true</code> <!-- -->ã§å¯¾è±¡åããããªãªã¸ã³ã® <code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">Cache-Control</code> ã䏿¸ãã㦠TTL ãå¼·å¶ããã®ã¯ <code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">cacheTtl</code> <!-- -->ã®å½¹å²</li><li class="text-[15px] leading-7 text-foreground/85 pl-5 relative before:content-['â'] before:absolute before:left-0 before:text-muted-foreground">ãã£ãã·ã¥ã¯ <strong>colo(ãã¼ã¿ã»ã³ã¿ã¼)åä½</strong>ã§ã°ãã¼ãã«å
±æã§ã¯ãªããä¸çä¸ããã®ã¢ã¯ã»ã¹ã¯ colo æ¯ã« 1 åã㤠API ãå©ããããã§ãå人ãµã¤ããªã GitHub API ã®æªèªè¨¼ã¬ã¼ãå¶é(60 req/h/IP)ã«åã¾ããã æ°ã«ãªãå ´å㯠Smart Tiered Cache(å
¨ãã©ã³ç¡æã»ãªããã¤ã³)ã§ä¸ä½ colo ã«éç´ã§ãã</li></ul><p class="text-[15px] leading-8 text-foreground/85 my-5">ãã1ã¤ãå®éã«è¸ãã ç½ : dev.to API ã User-Agent ãªãã®ãªã¯ã¨ã¹ããæå¦ãã<code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">Promise.allSettled</code> ã§æ¡ãã¤ã¶ãã¦ãããããZenn ã ã表示ãã㦠dev.to ãæ¶ãããç¶æ
ã«æ°ã¥ãã®ãé
ããã
6<mark class="bg-foreground/8 text-inherit px-1 py-0.5 box-decoration-clone">è¤æ°ã½ã¼ã¹ãæããã¨ãã¯ãçæ¹ã ã失æããä¸å®å ¨ãªçµæããã£ãã·ã¥ããªã</mark>ãã¨ã</p><h2 id="section-03" data-toc-index="03" class="mt-14 mb-6 pt-6 pb-4 border-t border-b border-rule flex items-baseline gap-4 scroll-mt-24"><span class="font-mono text-xs text-muted-foreground">03</span><span data-toc-title="true" class="text-xl sm:text-2xl font-bold tracking-tight">ConnectRPC 㯠NO_SIDE_EFFECTS ã§ GET ã«ãã</span></h2><p class="text-[15px] leading-8 text-foreground/85 my-5">èªå API(ConnectRPC)ã®èªã¿åã RPC ãåããã¿ã¼ã³ã«ä¹ããããããã Connect ã® RPC ã¯ããã©ã«ãã§ POST ãªã®ã§ã¨ãã¸ãã£ãã·ã¥ã«ä¹ããªããçµè«:<!-- --> <mark class="bg-foreground/8 text-inherit px-1 py-0.5 box-decoration-clone">proto ã« <code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">idempotency_level = NO_SIDE_EFFECTS</code> ãä»ãã㨠GET ã§å©ããããã«ãªã</mark>ã</p><figure class="group relative overflow-hidden border my-6 rounded-none border-rule bg-transparent"><figcaption class="flex items-center justify-between gap-2 pl-4 pr-2 py-1.5 border-b border-border font-mono text-[10px] tracking-wider text-muted-foreground">stats.proto<button data-slot="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xs text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive hover:bg-accent cursor-pointer size-7 text-muted-foreground hover:text-foreground" type="button" aria-label="ã³ã¼ããã³ãã¼"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy size-3.5" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></figcaption><pre class="p-4 overflow-x-auto"><code class="hljs font-mono text-[13px] leading-6 bg-transparent"><span class="hljs-keyword">service </span><span class="hljs-title class_">StatsService</span> { 7 <span class="hljs-function"><span class="hljs-keyword">rpc</span> GetPublicStats(GetPublicStatsRequest) <span class="hljs-keyword">returns</span> (GetPublicStatsResponse) </span>{ 8 <span class="hljs-keyword">option</span> idempotency_level = NO_SIDE_EFFECTS; 9 } 10}</code></pre></figure><p class="text-[15px] leading-8 text-foreground/85 my-5">ã¯ã©ã¤ã¢ã³ãå´ã¯ã³ã¼ãçæããä¸è¦ã§ãç´ ã® fetch ã§å©ãã:</p><figure class="group relative overflow-hidden border my-6 rounded-none border-rule bg-transparent"><figcaption class="flex items-center justify-between gap-2 pl-4 pr-2 py-1.5 border-b border-border font-mono text-[10px] tracking-wider text-muted-foreground">Connect GET ãããã³ã«ã® URL<button data-slot="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xs text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive hover:bg-accent cursor-pointer size-7 text-muted-foreground hover:text-foreground" type="button" aria-label="ã³ã¼ããã³ãã¼"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy size-3.5" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></figcaption><pre class="p-4 overflow-x-auto"><code class="hljs font-mono text-[13px] leading-6 bg-transparent">GET https://api.example.com/pkg.v1.StatsService/GetPublicStats 11 ?encoding=json&message=%7B%7D&connect=v1 12// â {"publicWallpaperCount":19, "creatorCount":6, "totalLicenseCount":"81"}</code></pre></figure>
12<p class="text-[15px] leading-8 text-foreground/85 my-5">ããã«ç½ ã1ã¤ãã¬ã¹ãã³ã¹æ«å°¾ãè¦ã㨠<code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">totalLicenseCount</code> ã ãæååã«ãªã£ã¦ããã<span class="underline decoration-klein/70 decoration-2 underline-offset-[5px]">proto ã® <code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">int64</code> 㯠JSON ã§ã¯ç²¾åº¦ä¿è·ã®ããæååã«ã·ãªã¢ã©ã¤ãºããã</span>仿§ã§ã<code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">Number()</code> ã§ã®å¤æãå¿ãã㨠<code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">NaN</code> ãæååé£çµãã°ã«ãªãã</p><figure class="my-8 border border-rule p-5 sm:p-6"><div class="group relative"><div class="w-full font-mono text-(--dg-ink) transition-opacity opacity-0" style="--dg-ink:var(--diagram-ink, var(--foreground));--dg-stroke:var(--diagram-stroke, var(--foreground));--dg-fill:var(--diagram-fill, var(--background));--dg-inverse-ink:var(--diagram-inverse-ink, var(--background));--dg-inverse-fill:var(--diagram-inverse-fill, var(--foreground));
12--dg-accent:var(--diagram-accent, var(--destructive));--dg-accent-ink:var(--diagram-accent-ink, var(--destructive-foreground));--dg-muted-ink:var(--diagram-muted-ink, var(--muted-foreground));--dg-muted-fill:var(--diagram-muted-fill, var(--muted));--dg-muted-stroke:var(--diagram-muted-stroke, var(--muted-foreground));--dg-line:var(--diagram-line, var(--border));height:320px"><div data-testid="rf__wrapper" style="width:100%;height:100%;overflow:hidden;position:relative;z-index:0" class="react-flow light" role="application"><div class="react-flow__renderer" style="position:absolute;width:100%;height:100%;top:0;left:0"><div class="react-flow__pane" style="position:absolute;width:100%;height:100%;top:0;left:0"><div class="react-flow__viewport xyflow__viewport react-flow__container" style="transform:translate(0px,0px) scale(1)"><div class="react-flow__edges"></div><div class="react-flow__edgelabel-renderer"></div><div class="react-flow__nodes" style="position:absolute;width:100%;height:100%;top:0;left:0"></div><div class="react-flow__viewport-portal"></div></div></div></div><div id="react-flow__node-desc-1" style="display:none">Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.</div><div id="react-flow__edge-desc-1" style="display:none">Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.</div><div id="react-flow__aria-live-1" aria-live="assertive" aria-atomic="true" style="position:absolute;width:1px;height:1px;margin:-1px;border:0;padding:0;overflow:hidden;clip:rect(0px, 0px, 0px, 0px);clip-path:inset(100%)"></div></div></div><button aria-label="å³ãå
¨ç»é¢ã§éã" class="absolute top-2 right-2 z-10 flex size-7 cursor-zoom-in items-center justify-center border border-(--dg-stroke) bg-(--dg-fill) text-(--dg-ink) opacity-50 transition-opacity hover:opacity-100 group-hover:opacity-80" style="--dg-ink:var(--diagram-ink, var(--foreground));--dg-stroke:var(--diagram-stroke, var(--foreground));--dg-fill:var(--diagram-fill, var(--background));--dg-inverse-ink:var(--diagram-inverse-ink, var(--background));--dg-inverse-fill:var(--diagram-inverse-fill, var(--foreground));
12--dg-accent:var(--diagram-accent, var(--destructive));--dg-accent-ink:var(--diagram-accent-ink, var(--destructive-foreground));--dg-muted-ink:var(--diagram-muted-ink, var(--muted-foreground));--dg-muted-fill:var(--diagram-muted-fill, var(--muted));--dg-muted-stroke:var(--diagram-muted-stroke, var(--muted-foreground));--dg-line:var(--diagram-line, var(--border))" type="button"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2 size-3.5" aria-hidden="true"><path d="M15 3h6v6"></path><path d="m21 3-7 7"></path><path d="m3 21 7-7"></path><path d="M9 21H3v-6"></path></svg></button></div><figcaption class="mt-4 border-t border-border pt-2.5 font-mono text-[10px] tracking-[0.1em] text-muted-foreground uppercase">Fig. 2 â ã¨ãã¸ãã£ãã·ã¥ã® hit / missã2 åç®ä»¥é㯠API ã«å°éããªã</figcaption></figure><h2 id="section-04" data-toc-index="04" class="mt-14 mb-6 pt-6 pb-4 border-t border-b border-rule flex items-baseline gap-4 scroll-mt-24"><span class="font-mono text-xs text-muted-foreground">04</span><span data-toc-title="true" class="text-xl sm:text-2xl font-bold tracking-tight">SSR ãã³ãã«ã®ãããã©ãã</span></h2><p class="text-[15px] leading-8 text-foreground/85 my-5">çµè«: Worker ã«ãã³ãã«ãããã®ã¯ãå®è¡ãããã³ã¼ããã§ã¯ãªã<mark class="bg-foreground
12/8 text-inherit px-1 py-0.5 box-decoration-clone">ãimport ãããã³ã¼ããå ¨é¨</mark>ãå ·ä½ä¾ã2ã¤ã</p><ul class="my-5 space-y-2"><li class="text-[15px] leading-7 text-foreground/85 pl-5 relative before:content-['â'] before:absolute before:left-0 before:text-muted-foreground"><strong>巨大ä¾åã SSR ãã³ãã«ã«å ¥ã</strong>: å³è¡¨æç»ã«ä½¿ã£ã¦ãã elkjs ã¯ã¯ã©ã¤ã¢ã³ãã§åç import ãã¦ãããããã£ã³ã¯ã¨ãã¦ã¯ Worker å´ã«ãå«ã¾ã <span class="underline decoration-klein/70 decoration-2 underline-offset-[5px]">gzip 537KB</span> ãå ãããWorkers ã®ãµã¤ãºä¸é(ç¡æ 3MB / ææ 10MB gzip)ã«è¿ã¥ãããæåã«çãå ´æ</li><li class="text-[15px] leading-7 text-foreground/85 pl-5 relative before:content-['â'] before:absolute before:left-0 before:text-muted-foreground"><strong>ç°å¢å¤æ°ã®åãåã</strong>: <code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">process.env</code> 㯠<code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">nodejs_compat</code> ãã©ã° + æ°ããã®äºææ¥ä»(æ¬ãµã¤ã㯠2025-09-02 ã§ç¢ºèª)ã§ Worker ã® env ããèªåçã«å±éããããsecret ã¯<!-- --> <code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">wrangler secret put</code>ããã¼ã«ã«ã¯ <code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">.dev.vars</code>(gitignore å¿ é )</li></ul><p class="text-[15px] leading-8 text-foreground/85 my-5">ãã1ã¤è¨è¨é¢ã§å¹ããã®ã¯ <strong>graceful degradation</strong>ãå飿ºã®åå¾é¢æ°ã¯å¤±ææã« <code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">null</code> / 空é åãè¿ããUI å´ã¯ãã»ã¯ã·ã§ã³ãã¨é表示ãã«åããå¤é¨ API ãå ¨é¨æ»ãã§ããã¼ã¸ã¯å£ãããè¡¨ç¤ºãæ¸ãã ãã«ãªãã</p><figure class="group relative overflow-hidden border my-6 rounded-none border-rule bg-transparent"><figcaption class="flex items-center justify-between gap-2 pl-4 pr-2 py-1.5 border-b border-border font-mono text-[10px] tracking-wider text-muted-foreground">失ææã¯ã»ã¯ã·ã§ã³é表示ã«åã<button data-slot="button" class="inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-xs text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive hover:bg-accent cursor-pointer size-7 text-muted-foreground hover:text-foreground" type="button" aria-label="ã³ã¼ããã³ãã¼"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-copy size-3.5" aria-hidden="true"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"></rect><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"></path></svg></button></figcaption><pre class="p-4 overflow-x-auto"><code class="hljs font-mono text-[13px] leading-6 bg-transparent"><span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> fetchStats = <span class="hljs-title function_">createServerFn</span>({ <span class="hljs-attr">method</span>: <span class="hljs-string">"GET"</span> }).<span class="hljs-title function_">handler</span>(<span class="hljs-title function_">async</span> () => { 13 <span class="hljs-keyword">try</span> { 14 <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> <span class="hljs-title function_">fetch</span>(<span class="hljs-variable constant_">API</span>, <span class="hljs-variable constant_">FETCH_INIT</span>); 15 <span class="hljs-keyword">if</span> (!response.<span class="hljs-property">ok</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>; <span class="hljs-comment">// â throw ããªã</span> 16 <span class="hljs-keyword">return</span> <span class="hljs-keyword">await</span> response.<span class="hljs-property">json</span><<span class="hljs-title class_">Stats</span>>(); 17 } <span class="hljs-keyword">catch</span> { 18 <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
18 <span class="hljs-comment">// â UI å´: {stats && <StatsBand />}</span> 19 } 20});</code></pre></figure><h2 id="section-05" data-toc-index="05" class="mt-14 mb-6 pt-6 pb-4 border-t border-b border-rule flex items-baseline gap-4 scroll-mt-24"><span class="font-mono text-xs text-muted-foreground">05</span><span data-toc-title="true" class="text-xl sm:text-2xl font-bold tracking-tight">ãã®æ§æãåãã±ã¼ã¹ã»åããªãã±ã¼ã¹</span></h2><p class="text-[15px] leading-8 text-foreground/85 my-5"><strong>åãã±ã¼ã¹</strong></p><ul class="my-5 space-y-2"><li class="text-[15px] leading-7 text-foreground/85 pl-5 relative before:content-['â'] before:absolute before:left-0 before:text-muted-foreground">å人ãµã¤ãã»ããã¥ã¡ã³ããµã¤ãã»LP: ã³ã³ãã³ããã³ã¼ã㨠API éç´ã§å®çµãã</li><li class="text-[15px] leading-7 text-foreground/85 pl-5 relative before:content-['â'] before:absolute before:left-0 before:text-muted-foreground">表示ããå¤é¨ãã¼ã¿ã«ãæ°æéã®é®®åº¦ãã§ååãªãã®(è¨äºä¸è¦§ã»çµ±è¨ã»SNS ãã£ã¼ã)</li></ul><p class="text-[15px] leading-8 text-foreground/85 my-5"><strong>åããªãã±ã¼ã¹</strong></p><ul class="my-5 space-y-2"><li class="text-[15px] leading-7 text-foreground/85 pl-5 relative before:content-['â'] before:absolute before:left-0 before:text-muted-foreground">æ¸ãè¾¼ã¿ã主ä½ã®ã¢ããª: ãã£ãã·ã¥æ¦ç¥ãããã¼ã¿ã¹ãã¢(D1 / å¤é¨ DB)ã®è¨è¨ãæ¯é çã«ãªã</li><li class="text-[15px] leading-7 text-foreground/85 pl-5 relative before:content-['â'] before:absolute before:left-0 before:text-muted-foreground">ãªã¢ã«ã¿ã¤ã æ§ãå¿ è¦ãªãã¼ã¿(ä¾¡æ ¼ã»å¨åº«ãªã©): TTL ãã£ãã·ã¥ã®æ©æµããªã</li><li class="text-[15px] leading-7 text-foreground/85 pl-5 relative before:content-['â'] before:absolute before:left-0 before:text-muted-foreground">colo ãè·¨ãã å³å¯ãªãã£ãã·ã¥ä¸è²«æ§ãå¿ è¦ãªå ´å: cf ãªãã·ã§ã³ã§ã¯å¶å¾¡ã§ãã KV / DO ãå¿ è¦</li></ul><p class="text-[15px] leading-8 text-foreground/85 my-5">éã«è¨ãã°ãèªã¿åãä¸å¿ã§é®®åº¦è¦ä»¶ãç·©ããµã¤ããªãããã®æ§æã¯ãç¡ææ ã»ããã㤠1 åã»éç¨ã¼ããã§åãã ã¾ã<!-- --> <code class="font-mono text-[13px] bg-secondary px-1.5 py-0.5">cf: <!-- -->{ cacheTtl, cacheEverything }</code> ã® 1 è¡ãã試ãã¦ã¿ã¦ã»ããã</p></div><footer class="mt-20 pt-8 border-t border-rule"><div class="flex items-baseline justify-between mb-8"><div class="caption text-foreground">More Articles</div><a tabindex="0" class="text-sm decoration-1 hover:decoration-2 decoration-border underline-offset-4 no-underline caption font-normal text-muted-foreground hover:text-foreground transition-colors active" href="/articles" data-status="active" aria-current="page">All Articles â</a></div><ul class="border-t border-rule"><li data-dimmed="false" class="border-b border-rule"><a href="https://zenn.dev/ken109/articles/cloud-tasks-emulator" target="_blank" rel="noopener noreferrer" class="group grid grid-cols-12 gap-x-6 gap-y-1 py-5 px-4 -mx-4 items-baseline transition-colors duration-200 hover:bg-klein-wash"><span class="col-span-12 sm:col-span-2 caption text-muted-foreground">2026.06.21</span><span class="col-span-12 sm:col-span-8 text-lg sm:text-xl font-extrabold leading-snug transition-colors duration-200 group-hover:text-klein">Cloud Tasks ã«ããã¼ã«ã«ã¨ãã¥ã¬ã¼ã¿ã欲ããã£ãã®ã§ä½ã£ã</span><span class="hidden sm:flex col-span-2 items-baseline justify-end caption tracking-[0.12em] text-muted-foreground group-hover:text-klein transition-colors duration-200">Zenn<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-up-right size-3 ml-1 self-center" aria-hidden="true"><path d="M7 7h10v10"></path><path d="M7 17 17 7"></path></svg></span></a></li><li data-dimmed="false" class="border-b border-rule"><a tabindex="0" href="/articles/testing-strategy-ai-e2e" class="text-sm decoration-1 hover:decoration-2 decoration-border underline-offset-4 group grid grid-cols-12 gap-x-6 gap-y-1 py-5 px-4 -mx-4 items-baseline transition-colors duration-200 hover:bg-klein-wash no-underline font-normal"><span class="col-span-12 sm:col-span-2 caption text-muted-foreground">2026.06.10</span><span class="col-span-12 sm:col-span-8 text-lg sm:text-xl font-extrabold leading-snug transition-colors duration-200 group-hover:text-klein">AI ã§ãã¹ãã®å·çã³ã¹ãã 0 ã«ãªã£ããå ¨é¨ E2E ã§ããã®ã â testcontainers ã¨ãã¹ãã㨠DB åé¢ã§èãã</span></a></li><li data-dimmed="false" class="border-b border-rule"><a href="https://zenn.dev/fluxlay/articles/fluxlay-claude-code-plugin" target="_blank" rel="noopener noreferrer" class="group grid grid-cols-12 gap-x-6 gap-y-1 py-5 px-4 -mx-4 items-baseline transition-colors duration-200 hover:bg-klein-wash"><span class="col-span-12 sm:col-span-2 caption text-muted-foreground">2026.05.04</span><span class="col-span-12 sm:col-span-8 text-lg sm:text-xl font-extrabold leading-snug transition-colors duration-200 group-hover:text-klein">Claude Code ã«ãåãå£ç´ããæ¸ããããã©ã°ã¤ã³ãä½ã£ã</span><span class="hidden sm:flex col-span-2 items-baseline justify-end caption tracking-[0.12em] text-muted-foreground group-hover:text-klein transition-colors duration-200">Zenn<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-arrow-up-right size-3 ml-1 self-center" aria-hidden="true"><path d="M7 7h10v10"></path><path d="M7 17 17 7"></path></svg></span></a></li></ul><div class="mt-10 flex justify-end"><a tabindex="0" href="/#contact" class="text-sm decoration-1 hover:decoration-2 decoration-border underline-offset-4 no-underline caption font-normal text-muted-foreground hover:text-foreground transition-colors">ãä»äºã®ãç¸è«ã¯ãã¡ã â</a></div></footer></div></article></div><!--/$--><!--/$--></main><footer class="relative mt-30"><div class="relative mx-auto max-w-[1440px] px-(--gutter)"><div class="border-t border-ink-900 pt-3.5 pb-12"><div class="flex flex-wrap items-baseline justify-between gap-x-5 gap-y-1.5 caption"><span>© <!-- -->2026<!-- --> Kensuke Kubo â ken109.dev</span><span class="flex flex-wrap gap-x-4"><a href="https://github.com/ken109" target="_blank" rel="noopener noreferrer" class="border-b border-rule hover:border-klein hover:text-klein">GitHub</a><a href="https://x.com/ken109_" target="_blank" rel="noopener noreferrer" class="border-b border-rule hover:border-klein hover:text-klein">X</a><a href="https://zenn.dev/ken109" target="_blank" rel="noopener noreferrer" class="border-b border-rule hover:border-klein hover:text-klein">Zenn</a><a href="https://dev.to/ken109" target="_blank" rel="noopener noreferrer" class="border-b border-rule hover:border-klein hover:text-klein">DEV</a></span></div></div></div></footer></div>
20<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:[]};$_TSR.router=($R=>$R[0]={manifest:$R[1]={routes:$R[2]={__root__:$R[3]={preloads:$R[4]=["/assets/index-ClIfQcG2.js"],assets:$R[5]=[$R[6]={tag:"link",attrs:$R[7]={rel:"stylesheet",href:"/assets/index-DtxX27Jz.css",type:"text/css"}},$R[8]={tag:"script",attrs:$R[9]={type:"module",async:!0},children:"import(\"/assets/index-ClIfQcG2.js\")"}]},"/articles/$slug":$R[10]={preloads:$R[11]=["/assets/articles._slug-x3piR9Zp.js","/assets/article-list-B3ruWv1x.js"]}}},matches:$R[12]=[$R[13]={i:"__root__�",u:1791240746638,s:"success",ssr:!0},$R[14]={i:"�articles�$slug�articles�edge-first-architecture",u:1791240746851,s:"success",l:$R[15]={slug:"edge-first-architecture",title:"å人ãµã¤ãããªãªã¸ã³ãµã¼ãã¼ãªãã§éç¨ãã â Cloudflare Workers SSR ã®æ§æã¨ãããã©ãã",description:"TanStack Start ã Cloudflare Workers ã§ SSR ããå¤é¨ API 飿ºãã¨ãã¸ãã£ãã·ã¥ã«æããæ§æã®å®è£ ãfetch ã® cf ãªãã·ã§ã³ãConnectRPC ã GET ã§å©ãæ¹æ³ãå®éã«ããã£ãç½ ã解説ããã",publishedAt:"2026-06-04",tags:$R[16]=["Cloudflare Workers","TanStack Start","ConnectRPC"],views:91},ssr:!0}],lastMatchId:"�articles�$slug�articles�edge-first-architecture",dehydratedData:$R[17]={queryStream:$R[18]=($R[19]=(e) => new ReadableStream({ start: (r) => { 21 e.on({ 22 next: (a) => { 23 try { 24 r.enqueue(a); 25 } catch (t) {} 26 }, 27 throw: (a) => { 28 r.error(a); 29 }, 30 return: () => { 31 try { 32 r.close(); 33 } catch (a) {} 34 } 35 }); 36} }))($R[20]=($R[21]=() => { 37 let e = [], r = [], t = !0, n = !1, a = 0, s = (l, g, S) => { 38 for (S = 0; S < a; S++) r[S] && r[S][g](l); 39 }, i = (l, g, S, d) => { 40 for (g = 0, S = e.length; g < S; g++) d = e[g], !t && g === S - 1 ? l[n ? "return" : "throw"](d) : l.next(d); 41 }, u = (l, g) => (t && (g = a++, r[g] = l), i(l), () => { 42 t && (r[g] = r[a], r[a--] = void 0); 43 }); 44 return { 45 __SEROVAL_STREAM__: !0, 46 on: (l) => u(l), 47 next: (l) => { 48 t && (e.push(l), s(l, "next")); 49 }, 50 throw: (l) => { 51 t && (e.push(l), s(l, "throw"), t = !1, n = !1, r.length = 0); 52 }, 53 return: (l) => { 54 t && (e.push(l), s(l, "return"), t = !1, n = !0, r.length = 0); 55 } 56 }; 57})()),dehydratedQueryClient:$R[22]={mutations:$R[23]=[],queries:$R[24]=[$R[25]={dehydratedAt:1791240746851,state:$R[26]={data:$R[27]=[$R[28]={slug:"zenn-cloud-tasks-emulator",title:"Cloud Tasks ã«ããã¼ã«ã«ã¨ãã¥ã¬ã¼ã¿ã欲ããã£ãã®ã§ä½ã£ã",description:"",publishedAt:"2026-06-21",tags:$R[29]=[],externalUrl:"https://zenn.dev/ken109/articles/cloud-tasks-emulator",source:"Zenn"},$R[30]={slug:"zenn-fluxlay-claude-code-plugin",title:"Claude Code ã«ãåãå£ç´ããæ¸ããããã©ã°ã¤ã³ãä½ã£ã",description:"",publishedAt:"2026-05-04",tags:$R[31]=[],externalUrl:"https://zenn.dev/fluxlay/articles/fluxlay-claude-code-plugin",source:"Zenn"},$R[32]={slug:"zenn-fluxlay-dev-dashboard-wallpaper",title:"å£ç´ã« PR ã¨ã«ã¬ã³ãã¼ã¨ Pomodoro ãåãè¾¼ãã ",description:"",publishedAt:"2026-05-03",tags:$R[33]=[],externalUrl:"https://zenn.dev/fluxlay/articles/fluxlay-dev-dashboard-wallpaper",source:"Zenn"},$R[34]={slug:"zenn-fluxlay-clickable-live-wallpaper",title:"ã¯ãªãã¯ã¹ã«ã¼ãªã©ã¤ãå£ç´ã®ä¸ã® React UI ãæä½å¯è½ã«ãã",description:"",publishedAt:"2026-05-03",tags:$R[35]=[],externalUrl:"https://zenn.dev/fluxlay/articles/fluxlay-clickable-live-wallpaper",source:"Zenn"},$R[36]={slug:"zenn-fluxlay-philosophy-live-wallpaper",title:"ãªãå£ç´ã®ãã©ãããã©ã¼ã ãä½ã£ã¦ããã®ã â Fluxlay ã®å²å¦",description:"",publishedAt:"2026-04-29",tags:$R[37]=[],externalUrl:"https://zenn.dev/fluxlay/articles/fluxlay-philosophy-live-wallpaper",source:"Zenn"},$R[38]={slug:"zenn-react-live-wallpaper-macos-fluxlay",title:"React ã§ macOS ã®ã©ã¤ãå£ç´ãä½ãããã¼ã«ãéçºãã",description:"",publishedAt:"2026-04-07",tags:$R[39]=[],externalUrl:"https://zenn.dev/fluxlay/articles/react-live-wallpaper-macos-fluxlay",source:"Zenn"},$R[40]={slug:"devto-i-built-a-live-wallpaper-engine-for-macos-heres-how-it-works-5ed",title:"I Built a Live Wallpaper Engine for macOS â Here's How It Works",description:"Your desktop wallpaper is a static image. It has been since the day you set it up. Mine was...",publishedAt:"2026-04-07",tags:$R[41]=["showdev","react","rust","webdev"],externalUrl:"https://dev.to/ken109/i-built-a-live-wallpaper-engine-for-macos-heres-how-it-works-5ed",source:"DEV"}],dataUpdateCount:1,dataUpdatedAt:1791240746851,error:null,errorUpdate
57Count:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:"success",fetchStatus:"idle"},queryKey:$R[42]=["external-articles"],queryHash:"[\"external-articles\"]"}]}}})($R["tsr"]);document.currentScript.remove()</script>
57<script type="module" async="">import("/assets/index-ClIfQcG2.js")</script>
57<script>($R=>$R[20].return(void 0))($R["tsr"]);$_TSR.e();document.currentScript.remove()</script>
57<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"344a403179da454db8c901a41ba25504","r":1,"spa":2}' crossorigin="anonymous"></script>
57 58</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.