<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: YukiOnishi</title>
    <description>The latest articles on DEV Community by YukiOnishi (@yukionishi1129).</description>
    <link>https://dev.to/yukionishi1129</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1000839%2F119750e9-d259-4ed1-8384-abf0340c81f2.jpeg</url>
      <title>DEV Community: YukiOnishi</title>
      <link>https://dev.to/yukionishi1129</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yukionishi1129"/>
    <language>en</language>
    <item>
      <title>Building a Production-Ready Next.js App Router Architecture: A Complete Playbook</title>
      <dc:creator>YukiOnishi</dc:creator>
      <pubDate>Mon, 13 Oct 2025 14:21:27 +0000</pubDate>
      <link>https://dev.to/yukionishi1129/building-a-production-ready-nextjs-app-router-architecture-a-complete-playbook-3f3h</link>
      <guid>https://dev.to/yukionishi1129/building-a-production-ready-nextjs-app-router-architecture-a-complete-playbook-3f3h</guid>
      <description>&lt;p&gt;The App Router gives us incredible primitives—Server Components, Server Actions, and explicit “use client” boundaries—but it doesn’t ship with a blueprint. After watching teams struggle with blurred layers and hydration chaos, I decided to publish the architecture we’ve battle-tested in production.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Repo: next-app-router-architecture&lt;br&gt;
– System guide: &lt;a href="https://github.com/YukiOnishi1129/next-app-router-architecture/tree/main/docs" rel="noopener noreferrer"&gt;docs/README.md&lt;/a&gt;&lt;br&gt;
– Frontend playbook: &lt;a href="https://github.com/YukiOnishi1129/next-app-router-architecture/tree/main/frontend/docs" rel="noopener noreferrer"&gt;frontend/docs/README.md&lt;/a&gt;&lt;br&gt;
– Checklists: &lt;a href="https://github.com/YukiOnishi1129/next-app-router-architecture/blob/main/docs/checklists.md" rel="noopener noreferrer"&gt;docs/checklists.md&lt;/a&gt;&lt;br&gt;
– GitHub: &lt;a href="https://github.com/YukiOnishi1129/next-app-router-architecture" rel="noopener noreferrer"&gt;github.com/YukiOnishi1129/next-app-router-architecture&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The Product: Request &amp;amp; Approval System
&lt;/h2&gt;

&lt;p&gt;Not a toy blog. A real app surface that stresses the App Router.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Persona&lt;/th&gt;
&lt;th&gt;What they do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Requester&lt;/td&gt;
&lt;td&gt;Draft requests, edit before submitting, track status, get notified on decisions.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Approver&lt;/td&gt;
&lt;td&gt;See pending approvals, take action with comments, dig into history.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Everyone&lt;/td&gt;
&lt;td&gt;Manage profile (name, email, password), change-email flow, reset password.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Under the hood: Google Identity Platform + NextAuth for auth, multi-role flows, notifications, audit-friendly DTO validation. Just enough scope to reveal real App Router problems—without drowning you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why App Router Needs a Playbook
&lt;/h2&gt;

&lt;p&gt;Common pain points we kept hitting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blurred boundaries – server-only code (handlers, repositories) leaking into client components. Works in dev, blows up in prod.&lt;/li&gt;
&lt;li&gt;Server Action sprawl – actions scattered inside random page.tsx, entangling features with routes.&lt;/li&gt;
&lt;li&gt;Hydration regressions – a stray "use client" bubbles up; suddenly half the layout becomes client-rendered.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We wanted a blueprint that says: “put this here, never import that there, and here’s lint to enforce it.” So we wrote one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 1 — Layer with Intent
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frontend/src/
├─ app/           # App Router: routes, layouts, metadata (thin)
├─ features/      # Domain bundles (auth, requests, approvals, settings)
├─ shared/        # Cross-cutting UI, layout chrome, providers, lib
└─ external/      # Server adapters (dto, handler, service, repository, client)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  app/ stays thin
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// frontend/src/app/requests/[requestId]/page.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RequestDetailPageTemplate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/features/requests/components/server/RequestDetailPageTemplate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/shared/types/next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RequestDetailPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/requests/[requestId]&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;highlight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
    &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;highlight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;highlight&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;highlight&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RequestDetailPageTemplate&lt;/span&gt;
      &lt;span class="na"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;highlightCommentId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;highlight&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No data fetching inside the page. It forwards typed params to a feature template. That’s the rule everywhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  features/ own orchestration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;features/requests/
├─ components/
│  ├─ server/    # Server Components (page templates)
│  └─ client/    # Container / Presenter / Hook slices
├─ hooks/        # TanStack Query + client logic
├─ queries/      # Query keys + DTO helpers
├─ actions/      # Server Actions (thin wrappers)
└─ types/        # Shared types, enums
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Client slices are deliberately small:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;components/client/RequestList/
├─ RequestListContainer.tsx   # uses hook, passes props down
├─ RequestListPresenter.tsx   # pure JSX
├─ useRequestList.ts          # orchestrates Query + derived state
├─ RequestList.test.tsx       # co-located tests
└─ index.ts                   # barrel export
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  external/ is your escape hatch
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;external/
├─ dto/           # Zod schemas + TS types
├─ handler/       # Server entry points (command.server.ts / query.server.ts / *.action.ts)
├─ service/       # Domain services (business logic)
├─ repository/    # DB access (Drizzle or swap later)
└─ client/        # Outbound API clients
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because features never import repositories/services directly, swapping backends is painless: re-implement the service layer (e.g., point the repository to Go microservices instead of Drizzle) without touching React code. This pattern lets you move from “Next.js monolith” → “Next.js + Go backend” by rewiring services only.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lint keeps us honest
&lt;/h3&gt;

&lt;p&gt;Custom rules in frontend/eslint-local-rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;restrict-service-imports — only handlers can import external/service/**.&lt;/li&gt;
&lt;li&gt;restrict-action-imports — client components/hooks are the only consumers of *.action.ts.&lt;/li&gt;
&lt;li&gt;use-nextjs-helpers — enforce PageProps/LayoutProps, next/navigation, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They run in CI; architecture violations fail the build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Principle 2 — Routes &amp;amp; Layouts Drive Experience
&lt;/h2&gt;

&lt;p&gt;Route groups map to auth states:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;(guest) — login, signup, email-change flows&lt;/li&gt;
&lt;li&gt;(authenticated) — dashboard, requests, approvals, settings&lt;/li&gt;
&lt;li&gt;(neutral) — password reset, terms&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Layouts enforce access and render shared chrome.
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// frontend/src/app/(authenticated)/layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AuthenticatedLayoutWrapper&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/shared/components/layout/server/AuthenticatedLayoutWrapper&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;LayoutProps&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/shared/types/next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;force-dynamic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;AuthenticatedLayout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LayoutProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AuthenticatedLayoutWrapper&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;AuthenticatedLayoutWrapper&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AuthenticatedLayoutWrapper handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;requireAuthServer() check&lt;/li&gt;
&lt;li&gt;Query hydration via HydrationBoundary&lt;/li&gt;
&lt;li&gt;Rendering the sidebar + header
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// frontend/src/app/(authenticated)/requests/layout.tsx&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Request List | Request &amp;amp; Approval System&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Browse, filter, and search submitted requests.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Checklist for adding a route (from docs/checklists.md):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pick route group; create layout.tsx + page.tsx.&lt;/li&gt;
&lt;li&gt;Use LayoutProps&amp;lt;'/path'&amp;gt;; export metadata from the layout.&lt;/li&gt;
&lt;li&gt;In the page, await props.params / props.searchParams via PageProps.&lt;/li&gt;
&lt;li&gt;Delegate to a feature template under components/server.&lt;/li&gt;
&lt;li&gt;Add loading.tsx / error.tsx if the route does real work.&lt;/li&gt;
&lt;li&gt;Run pnpm typegen to refresh typed routes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Principle 3 — Server-First Data Fetching (with TanStack Query)
&lt;/h2&gt;

&lt;p&gt;We lean hard on TanStack Query—server and client.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server templates hydrate the cache
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// features/requests/components/server/RequestsPageTemplate.tsx&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;dehydrate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HydrationBoundary&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RequestList&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/features/requests/components/client/RequestList&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;requestKeys&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/features/requests/queries/keys&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ensureRequestListResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;selectRequestListFetcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/features/requests/queries/requestList.helpers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getQueryClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/shared/lib/query-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;listAllRequestsServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;listAssignedRequestsServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;listMyRequestsServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/external/handler/request/query.server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RequestFilterInput&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RequestsStatusTabKey&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/features/requests/types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RequestsPageTemplate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
  &lt;span class="nx"&gt;activeTabKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;RequestFilterInput&lt;/span&gt;
  &lt;span class="nx"&gt;activeTabKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestsStatusTabKey&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getQueryClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prefetchQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;selectRequestListFetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;listMine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listMyRequestsServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;listAssigned&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listAssignedRequestsServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;listAll&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listAllRequestsServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ensureRequestListResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"space-y-6 px-6 py-8"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* header, tabs */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HydrationBoundary&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;dehydrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RequestList&lt;/span&gt; &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;HydrationBoundary&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;section&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Takeaways&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server prefetch chooses the right handler (mine/assigned/all).&lt;/li&gt;
&lt;li&gt;ensureRequestListResponse validates DTOs before React.&lt;/li&gt;
&lt;li&gt;HydrationBoundary hands a warm cache to the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Client hook consumes the same key
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// features/requests/queries/useRequestListQuery.ts&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useQuery&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;requestKeys&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/features/requests/queries/keys&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;ensureRequestListResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;selectRequestListFetcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/features/requests/queries/requestList.helpers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;listAllRequestsAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;listAssignedRequestsAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;listMyRequestsAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/external/handler/request/query.action&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RequestFilterInput&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/features/requests/types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;useRequestListQuery&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestFilterInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nf"&gt;useQuery&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;queryFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fetcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;selectRequestListFetcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;listMine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listMyRequestsAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;listAssigned&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listAssignedRequestsAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;listAll&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listAllRequestsAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;ensureRequestListResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The container hook stays identical on first render and refetch, thanks to server hydration. No SSR/CSR divergence.&lt;/p&gt;

&lt;h3&gt;
  
  
  Container ➜ Hook ➜ Presenter
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// features/requests/components/client/RequestList/RequestListContainer.tsx&lt;/span&gt;
&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RequestListPresenter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./RequestListPresenter&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useRequestList&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./useRequestList&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RequestFilterInput&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/features/requests/types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;RequestListContainer&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestFilterInput&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;summaries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isRefetching&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;errorMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRequestList&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;filters&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RequestListPresenter&lt;/span&gt;
      &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;summaries&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;isLoading&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isLoading&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;isRefetching&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isRefetching&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;errorMessage&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errorMessage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The container memoizes filters, calls useRequestListQuery, maps DTOs to UI-friendly summaries. Presenters render what they’re given—nothing more.&lt;/p&gt;

&lt;h3&gt;
  
  
  When to skip hydration
&lt;/h3&gt;

&lt;p&gt;Static server views (e.g., an overview header that does a Promise.all and renders summary stats) don’t need TanStack Query. If it’s static, keep it server-only.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mutations invalidate precisely
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// features/approvals/hooks/useApproveRequest.ts&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;useMutation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;mutationFn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;approveRequestAction&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to approve request&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;requestId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidateQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;approvalKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pending&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidateQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;detail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidateQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidateQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requestKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;requestId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="nx"&gt;queryClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invalidateQueries&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;queryKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;notificationKeys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Principle 4 — Quality Gates Codified
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Custom ESLint rules (local)
&lt;/h3&gt;

&lt;p&gt;frontend/eslint-local-rules houses architecture cops:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;restrict-service-imports — prevent client code from touching external/service/**.&lt;/li&gt;
&lt;li&gt;restrict-action-imports — ensure Server Actions are only consumed by clients/hooks.&lt;/li&gt;
&lt;li&gt;use-nextjs-helpers — require PageProps/LayoutProps, next/navigation, and catch missing await props.params.&lt;/li&gt;
&lt;li&gt;use-client-check, use-server-check — verify directive placement.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run pnpm lint; violations fail fast.&lt;/p&gt;

&lt;h3&gt;
  
  
  DTO validation everywhere
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// external/dto/request/ensureRequestListResponse.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RequestListResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RequestListResult&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_LIMIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ensureRequestListResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RequestListResponse&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;RequestListResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Failed to load requests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;total&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;DEFAULT_LIMIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the backend changes shape, you know immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Error boundaries per route group
&lt;/h3&gt;

&lt;p&gt;Every major section has error.tsx and loading.tsx so failures surface gracefully, not as white screens.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tests live next to subjects
&lt;/h3&gt;

&lt;p&gt;Hooks, presenters, and server templates ship with co-located tests (*.test.ts(x)) using Vitest + React Testing Library. Open, run, change, repeat.&lt;/p&gt;

&lt;h3&gt;
  
  
  External layer = future-proof integration
&lt;/h3&gt;

&lt;p&gt;Because the UI only speaks to handlers, migrating from “Next.js + Drizzle” to “Next.js + Go microservices” is flipping the implementation inside external/service/**. Features, hooks, and components stay untouched.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lessons Learned &amp;amp; Scaling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Boundaries reduce mental load. When /app is routing-only, /features is orchestration, and /external is integration, PR reviews become trivial.&lt;/li&gt;
&lt;li&gt;A warm TanStack Query cache keeps UX snappy. Server-first fetching + client hydration gives you SSR speed and CSR interactivity.&lt;/li&gt;
&lt;li&gt;Custom lint beats tribal knowledge. Architecture decisions encoded as ESLint rules eliminate “you forgot to…” conversations.&lt;/li&gt;
&lt;li&gt;The external adapter pattern buys growth options. Offload heavy domains to Go? Point the service layer there; React stays the same.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  When should you adopt this playbook?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You’re shipping multi-role apps with significant server logic.&lt;/li&gt;
&lt;li&gt;You rely on Server Actions and RSC but need strict separation.&lt;/li&gt;
&lt;li&gt;You want to keep the door open for future backend refactors without touching React code.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dive Deeper
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/YukiOnishi1129/next-app-router-architecture" rel="noopener noreferrer"&gt;github.com/YukiOnishi1129/next-app-router-architecture&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;System guide: &lt;a href="https://github.com/YukiOnishi1129/next-app-router-architecture/tree/main/docs" rel="noopener noreferrer"&gt;docs/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Frontend playbook: &lt;a href="https://github.com/YukiOnishi1129/next-app-router-architecture/tree/main/frontend/docs" rel="noopener noreferrer"&gt;frontend/docs/README.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Checklists: &lt;a href="https://github.com/YukiOnishi1129/next-app-router-architecture/blob/main/docs/checklists.md" rel="noopener noreferrer"&gt;docs/checklists.md&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Clone it, explore the slices, and try adding a new feature by following the checklist. Once you’ve felt the predictability, you won’t want to go back to ad-hoc App Router projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  Appendix — .eslintrc.cjs (excerpt)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// frontend/.eslintrc.cjs&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@typescript-eslint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-hooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eslint-plugin-local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;extends&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/core-web-vitals&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;plugin:@typescript-eslint/recommended&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local/restrict-service-imports&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local/restrict-action-imports&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local/use-nextjs-helpers&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-hooks/rules-of-hooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;import/resolver&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;typescript&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./tsconfig.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Appendix — Typed Next.js helpers
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// shared/types/next.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ReadonlyURLSearchParams&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/navigation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;LayoutProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;_Path&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;children&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReactNode&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PageProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;_Path&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;searchParams&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ReadonlyURLSearchParams&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Appendix — Query client helper
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// shared/lib/query-client.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;QueryClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@tanstack/react-query&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getQueryClient&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;QueryClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;defaultOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;queries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;staleTime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;refetchOnWindowFocus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>typescript</category>
      <category>nextjs</category>
      <category>react</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Benefits and Drawbacks of Adopting Clean Architecture</title>
      <dc:creator>YukiOnishi</dc:creator>
      <pubDate>Sat, 19 Oct 2024 08:31:37 +0000</pubDate>
      <link>https://dev.to/yukionishi1129/benefits-and-drawbacks-of-adopting-clean-architecture-2pd1</link>
      <guid>https://dev.to/yukionishi1129/benefits-and-drawbacks-of-adopting-clean-architecture-2pd1</guid>
      <description>&lt;p&gt;Clean Architecture is a design principle aimed at organizing dependencies within a system, promoting flexibility and maintainability in software development. Below are the key benefits and drawbacks of adopting Clean Architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Benefits
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Improved Maintainability
&lt;/h3&gt;

&lt;p&gt;Clean Architecture separates business logic and domain-related code from infrastructure and frameworks, minimizing the impact of changes in requirements or technology. This makes the code easier to maintain over time.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Easier Testing
&lt;/h3&gt;

&lt;p&gt;Since business logic is isolated from other parts of the system, unit tests can be written more easily. You can test the business logic without relying on infrastructure or UI components, making the test code simpler and faster to execute.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Increased Flexibility and Reusability
&lt;/h3&gt;

&lt;p&gt;The architecture is divided into independent modules, making it easy to reuse specific components or features in other projects. Additionally, changing technology stacks (such as databases or frontend frameworks) can be done without affecting other layers of the system.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Independence from Technical Details
&lt;/h3&gt;

&lt;p&gt;Clean Architecture uses interfaces and abstract classes to decouple business logic from technical details like databases, UI, or external APIs. This enables easier modification of underlying technologies without impacting the core logic.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Clear Structure
&lt;/h3&gt;

&lt;p&gt;With distinct separation of responsibilities, Clean Architecture provides a clear structure, making it easier for new team members to understand the project and locate the necessary code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Drawbacks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Increased Initial Development Cost
&lt;/h3&gt;

&lt;p&gt;Adopting Clean Architecture requires significant upfront design work, including defining layers, interfaces, and separation of concerns. For small or short-term projects, this overhead may not be justified.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Increased Complexity
&lt;/h3&gt;

&lt;p&gt;With multiple layers involved, the overall complexity of the project can increase. For small-scale applications, Clean Architecture might feel overly complex and lead to reduced development efficiency due to the increased back-and-forth between layers.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Steep Learning Curve
&lt;/h3&gt;

&lt;p&gt;To implement Clean Architecture effectively, developers need to have a good understanding of concepts like abstraction and dependency inversion. This requires a certain level of experience and familiarity with architectural design patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Risk of Over-Engineering
&lt;/h3&gt;

&lt;p&gt;For small projects or simple systems, applying Clean Architecture strictly may lead to over-engineering. Adding too much abstraction or unnecessary layers could slow down development and make maintenance more complicated.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Potential Performance Overhead
&lt;/h3&gt;

&lt;p&gt;Using multiple layers and interfaces can introduce some performance overhead. While this is usually negligible, in real-time systems or performance-critical applications, this could be a concern and should be carefully evaluated.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Clean Architecture is particularly effective for large-scale systems where long-term maintainability and extensibility are important. However, for smaller or short-term projects, it may introduce unnecessary complexity and cost. It's essential to carefully assess whether adopting Clean Architecture is appropriate for the specific context of your project. When used correctly, it can be a very powerful architectural approach.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>webdev</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to study React to become a pro. Introduction to React.</title>
      <dc:creator>YukiOnishi</dc:creator>
      <pubDate>Mon, 10 Jun 2024 02:56:34 +0000</pubDate>
      <link>https://dev.to/yukionishi1129/how-to-study-react-to-become-a-pro-introduction-to-react-p3l</link>
      <guid>https://dev.to/yukionishi1129/how-to-study-react-to-become-a-pro-introduction-to-react-p3l</guid>
      <description>&lt;p&gt;React has by far the largest market share among JavaScript frameworks!&lt;/p&gt;

&lt;p&gt;However, it is a framework with a high failure rate, so I would like to introduce a roadmap for learning React from my knowledge as an active front-end engineer!&lt;/p&gt;

&lt;h1&gt;
  
  
  Understanding React overview
&lt;/h1&gt;

&lt;p&gt;First of all, what can React do? And what features does it have to make it work? Let's learn about the following!&lt;/p&gt;

&lt;h2&gt;
  
  
  The best framework for building SPA
&lt;/h2&gt;

&lt;p&gt;One of the biggest advantages of JavaScript frameworks such as React is the ability to efficiently build single page application (SPA)!&lt;/p&gt;

&lt;p&gt;Simply put, SPA is a technology that 'allows screen transitions to be carried out without waiting for a server response'. This feature allows for quick screen transitions.&lt;/p&gt;

&lt;p&gt;The mechanism that makes this SPA possible is actually achieved by "making it look like a screen transition is taking place by replacing the part below the body tag in the HTML"!&lt;/p&gt;

&lt;p&gt;With React, each time the screen URL changes, only the display part of the HTML is replaced.&lt;/p&gt;

&lt;p&gt;So how does it change parts of the HTML?&lt;/p&gt;

&lt;p&gt;To find out, you first need to understand something called 'components'.&lt;/p&gt;

&lt;h2&gt;
  
  
  The concept of 'components'
&lt;/h2&gt;

&lt;p&gt;React allows HTML to be described in JavaScript, and parts can be created in units of screen components, such as buttons, headers and footers.&lt;/p&gt;

&lt;p&gt;These component parts are called 'components', and screens are built by combining components.&lt;/p&gt;

&lt;p&gt;Screen transitions in SPA are achieved by replacing these components.&lt;/p&gt;

&lt;p&gt;This method of describing HTML in JavaScript is called 'jsx', and React uses this method to create component files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0wb4cn0jpxzsmqtpxqq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0wb4cn0jpxzsmqtpxqq.png" alt="Image description" width="800" height="509"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this case, the side to be introduced is called the parent component and the side to be introduced is called the child component.&lt;/p&gt;

&lt;p&gt;Furthermore, components are created as JavaScript files, so they are introduced by importing the child component's js file into the parent component's js file.&lt;/p&gt;

&lt;p&gt;At this point, the JavaScript function "ECMA module" is used, so make sure you have a good review of it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Change only the parts of the data that have been updated (about the virtual DOM)
&lt;/h2&gt;

&lt;p&gt;One of the advantages of using React is that "only the parts affected by the updated data are changed".&lt;/p&gt;

&lt;p&gt;In the case of web services that do not use React, even if only a part of the data is changed, synchronous communication is used and the entire screen has to be redrawn, which is not easy to operate. (Asynchronous communication is also possible using ajax, but this tends to result in complex and poorly maintainable code.)&lt;/p&gt;

&lt;p&gt;With React, it is possible to "redraw only the parts of the screen related to the updated data".&lt;/p&gt;

&lt;p&gt;Therefore, this can be achieved with simple code and without redrawing via synchronous communication.&lt;/p&gt;

&lt;p&gt;This can be achieved by differential rendering using the Virtual DOM.&lt;/p&gt;

&lt;p&gt;The concept of the virtual DOM is a little difficult to grasp, so we would like you to have an image of it as "having screen data before and after changes, and having a function that detects the differences and updates only the changed parts".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhiugd4hpxi2cqiwhm7y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhiugd4hpxi2cqiwhm7y.png" alt="Image description" width="800" height="465"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Components have states (State and Props)
&lt;/h2&gt;

&lt;p&gt;We have seen that changes to the screen UI are efficiently implemented by the virtual DOM.&lt;/p&gt;

&lt;p&gt;So how can we determine the sign that the UI has changed?&lt;/p&gt;

&lt;p&gt;Actually, each component can have its own data (state), and the change in state is used to determine that the UI has changed. (In React, a state is called a 'State'.)&lt;/p&gt;

&lt;p&gt;You can define states for components using functions such as 'useState' in React Hooks.&lt;/p&gt;

&lt;p&gt;Components can also be nested, so you can pass states defined in a parent component to a child component.&lt;/p&gt;

&lt;p&gt;This functionality is called 'Props', and in React, States and Props are the foundation of state management, so it is necessary to have a good understanding of them.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frh5b2l22xt6stz8dj3ia.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frh5b2l22xt6stz8dj3ia.png" alt="Image description" width="800" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  State management is fundamental in React!
&lt;/h2&gt;

&lt;p&gt;React provides a comfortable UI through state management using States and Props.&lt;/p&gt;

&lt;p&gt;In addition to Props, there is also a function called 'Context API' for passing state within components.&lt;/p&gt;

&lt;p&gt;There is also a function called "Redux" that enables this, as there are situations where you may want to use a common state for components that are not in a parent-child relationship. (Functions that manage state outside of components, such as Redux, are sometimes referred to as 'global state' or 'store'.)&lt;/p&gt;

&lt;p&gt;Thus, state management is very important in React!&lt;/p&gt;

&lt;p&gt;Since React does things in invisible areas such as the virtual DOM, it is difficult to get an image of how the state changes and the UI is actually changed behind the scenes.&lt;/p&gt;

&lt;p&gt;Therefore, it is important to first implement an application that changes the UI by state management using only JavaScript, and understand how the UI changes based on the flow of data.&lt;/p&gt;

&lt;p&gt;If you have never created a todo list in JavaScript, please try implementing it before learning React!&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;There are four key aspects to learning React.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The best framework for building SPA&lt;/li&gt;
&lt;li&gt;Components&lt;/li&gt;
&lt;li&gt;Virtual DOM&lt;/li&gt;
&lt;li&gt;State Management&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>react</category>
      <category>beginners</category>
      <category>javascript</category>
      <category>frontend</category>
    </item>
    <item>
      <title>【Reducing costs】Automatically remove old docker container image using Artifact Registry cleanup policies</title>
      <dc:creator>YukiOnishi</dc:creator>
      <pubDate>Sat, 01 Jun 2024 17:39:18 +0000</pubDate>
      <link>https://dev.to/yukionishi1129/reducing-costs-automatically-remove-old-docker-container-image-using-artifact-registry-cleanup-policies-734</link>
      <guid>https://dev.to/yukionishi1129/reducing-costs-automatically-remove-old-docker-container-image-using-artifact-registry-cleanup-policies-734</guid>
      <description>&lt;p&gt;When dealing with docker container images in Google Cloud, Artifact Registry is used as a storage location for them. &lt;br&gt;
However, as you store container images, more and more old images will remain, and you will eventually reach the capacity beyond the free quota.&lt;br&gt;
This costs you more and more money.&lt;/p&gt;

&lt;p&gt;However, Google cloud has 'cleanup policies' that automatically deletes old container images.&lt;/p&gt;

&lt;p&gt;This article summarises how to use that cleanup policies to automatically remove old container images.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Artifact Registry?
&lt;/h2&gt;

&lt;p&gt;Artifact Registry is a Google Cloud service that can complement and manage docker container images. You can operate and execute applications by linking the images stored here to CloudRun or CloudRun Jobs.&lt;/p&gt;

&lt;p&gt;By integrating with services such as Cloud Build, it is also possible to automatically store container images and set up automatic deployment to services like Cloud Run.&lt;/p&gt;

&lt;p&gt;There is also a similar service called Container Registry, but this one is deprecated and it is recommended to use Artifact Registry.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/artifact-registry/docs/overview?hl=en" rel="noopener noreferrer"&gt;Further information.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, Artifact Registry is required when dealing with container images in Google Cloud.&lt;/p&gt;

&lt;h3&gt;
  
  
  Artifact Registry pricing
&lt;/h3&gt;

&lt;p&gt;As for the pricing that you may be concerned about, the configurations are categorized into the following three types.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Storage&lt;/li&gt;
&lt;li&gt;Data transfer&lt;/li&gt;
&lt;li&gt;Vulnerability scanning&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Data transfer is free f charge within the same location.&lt;/p&gt;

&lt;p&gt;Storage is what my be charged for here.&lt;br&gt;
Up to 0.5GB of capacity is free, but beyond that it costs about $0.10 per month in 1GB increments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/artifact-registry/pricing?hl=en" rel="noopener noreferrer"&gt;Further information.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In my case, I use three kind of container images, a image's capacity is 11MB. So, all of my container image's capacity is 30~50MB. It is not over 0.5GB within free.&lt;/p&gt;

&lt;p&gt;In my case, I run about three container images, which are about 11MB each, so all together are about 30~50MB. I never exceed the free quota of 0.5MB.&lt;/p&gt;

&lt;p&gt;However, if Artifact Registry is not specifically configured, the old container image will remain after each reflection of the latest container image.&lt;/p&gt;

&lt;p&gt;As these unused images accumulate, they exceed 0.5GB by leeway.&lt;/p&gt;

&lt;p&gt;So old container images need to be deleted, but I would like to be automatic.&lt;/p&gt;

&lt;p&gt;It is the cleanup policies that can do this.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's cleanup policies?
&lt;/h2&gt;

&lt;p&gt;Cleanup policy is a function that automatically deletes container images, etc, stored in the Artifact Registry based on pre-defined content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/artifact-registry/docs/repositories/cleanup-policy?hl=en" rel="noopener noreferrer"&gt;Further information.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  delete policy and conditional keep policy
&lt;/h3&gt;

&lt;p&gt;The cleanup policy is designed to set two types.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;delete policy&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;conditional keep policy&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As the name suggests, these set the conditions for deleting and retaining images respectively.&lt;/p&gt;

&lt;p&gt;It is important to note that if only a "condition keep policy" is set, nothing is deleted. Container images will not be performed unless a "delete policy" is set.&lt;/p&gt;

&lt;p&gt;So, for example, "You want to saved latest two container image, delete others" &lt;/p&gt;

&lt;p&gt;So, for example, if you want to "keep the two most recent container images and delete everything else", the setting must be set up as follows.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;delete policy: &lt;strong&gt;Remove images with or without tags.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;condition keep policy: &lt;strong&gt;Only the two most recent versions are retained.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I write after about especially methods. &lt;/p&gt;

&lt;p&gt;Specific settings are described below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Constraints
&lt;/h3&gt;

&lt;p&gt;Cleanup policies have the following restrictions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deletions and retentions triggered by the clean-up policy are performed once per day (it is not known at what point in time they are performed).&lt;/li&gt;
&lt;li&gt;Deletions triggered by delete policies are up to 30,000 per repository, per day&lt;/li&gt;
&lt;li&gt;Up to 10 clean-up policies per repository&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is important to note that &lt;strong&gt;the clean-up policy is only executed once a day.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is also not clear at what time of the day it is executed, so depending on the timing, container images may not be removed.&lt;/p&gt;

&lt;p&gt;If you have automated CloudBuild or Github Actions to reflect the main repository in the ArtifactRegistry every time it is updated, and you reflect it many times a day, in some cases the container image may remain undeleted.&lt;/p&gt;

&lt;p&gt;The policy will be executed again the next day and the image will be deleted, but be aware that if the storage capacity is squeezed while it is not deleted, the billing amount may increase.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to set up automatically deletion
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Condition of automatically deletion
&lt;/h3&gt;

&lt;p&gt;In this case, the container image is set to be automatically deleted with the following content.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only the two most recent container images are retained, all other images are deleted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, set up a "delete policy" and "keep condition policy" with the following details.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;delete policy: remove images with or without tags.&lt;/li&gt;
&lt;li&gt;condition keep policy: only the two most recent versions are retained.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How to set up
&lt;/h3&gt;

&lt;p&gt;Go to Google Cloud and navigate to the Artifact Registry page.&lt;br&gt;
A list of repositories will appear, select the target repository.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fec7u5c05b58vabpr9nba.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fec7u5c05b58vabpr9nba.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on "EDIT REPOSITORY"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4dh0zac8r644398kium6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4dh0zac8r644398kium6.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Scroll to below, select "Cleanup policies" and "Delete artifacts".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtvzidsxylnr0l4qehky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqtvzidsxylnr0l4qehky.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here, set up "delete policy" and "keep condition".&lt;/p&gt;

&lt;p&gt;Click on "ADD A CLEANUP POLICY".&lt;/p&gt;

&lt;p&gt;First, set "delete policy".&lt;/p&gt;

&lt;p&gt;Fill in "name" and select "Conditional delete".&lt;/p&gt;

&lt;p&gt;Leave the "Tag state" as "Any tag state".&lt;/p&gt;

&lt;p&gt;Finally, click on "DONE" in the bottom right-hand corner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmg1wx00paysblsb743b2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmg1wx00paysblsb743b2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Second, "condition keep policy".&lt;/p&gt;

&lt;p&gt;Click on "ADD A CLEANUP POLICY" again to create a new policy.&lt;/p&gt;

&lt;p&gt;Fill in "name" and select "Keep most recent versions" as the policy  types.&lt;/p&gt;

&lt;p&gt;"Keep points" should be "2".&lt;/p&gt;

&lt;p&gt;Finally, click on "DONE" in the bottom right-hand corner.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ygk8mooph0hzpxeehs7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5ygk8mooph0hzpxeehs7.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Finally, click on the "Update" button at the bottom and you are done.&lt;/p&gt;

&lt;p&gt;The timing of when the policy is executed is random, but after some time only the two latest versions of the images in the repository will be available.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary.
&lt;/h2&gt;

&lt;p&gt;"Cleanup policies" can now be set to automatically remove old container images.&lt;/p&gt;

&lt;p&gt;If you use Artifact Registry, set this to avoid unnecessary charges.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devops</category>
      <category>googlecloud</category>
      <category>cloud</category>
    </item>
  </channel>
</rss>
