|
| 1 | +"use client"; |
| 2 | + |
| 3 | +import {type JSX} from "react"; |
| 4 | +import {Alert} from "@mui/material"; |
| 5 | +import {DocContent} from "../../components/doc-content/doc-content.js"; |
| 6 | + |
| 7 | +export const AppStructureScreen = (): JSX.Element => ( |
| 8 | + <DocContent> |
| 9 | + <h1>Application Structure</h1> |
| 10 | + <p> |
| 11 | + A well-structured ReCA application follows a <strong>layered architecture</strong>{" "} |
| 12 | + inspired by Clean Architecture and Feature-Sliced Design. The goal is simple: |
| 13 | + business logic lives in <code>src/</code>, completely independent of the framework. |
| 14 | + Next.js (or any other framework) is used only for routing. |
| 15 | + </p> |
| 16 | + |
| 17 | + <h2>Top-Level Layout</h2> |
| 18 | + <pre><code>{`my-app/ |
| 19 | +├── app/ ← Next.js routing only (no business logic here) |
| 20 | +├── src/ ← all application code (framework-agnostic) |
| 21 | +│ ├── components/ |
| 22 | +│ ├── screens/ |
| 23 | +│ ├── services/ |
| 24 | +│ ├── repositories/ |
| 25 | +│ ├── models/ |
| 26 | +│ ├── hooks/ |
| 27 | +│ ├── helpers/ |
| 28 | +│ ├── utils/ |
| 29 | +│ ├── validations/ |
| 30 | +│ ├── localization/ |
| 31 | +│ ├── styles/ |
| 32 | +│ └── icons/ |
| 33 | +├── public/ |
| 34 | +├── tests/ |
| 35 | +└── package.json`}</code></pre> |
| 36 | + |
| 37 | + <h2>app/ — Routing Only</h2> |
| 38 | + <p> |
| 39 | + The <code>app/</code> directory belongs entirely to Next.js App Router. Every file |
| 40 | + here is a thin routing shell: it imports a page component from <code>src/pages/</code> |
| 41 | + and renders it inside the layout. <strong>No business logic, no stores, no |
| 42 | + services here.</strong> |
| 43 | + </p> |
| 44 | + <pre><code>{`// app/products/page.tsx ← just a route entry point |
| 45 | +"use client"; |
| 46 | +
|
| 47 | +import type { JSX } from "react"; |
| 48 | +import { Shell } from "../../src/components/shell/Shell"; |
| 49 | +import { Layout } from "../../src/components/layout/Layout"; |
| 50 | +import { ProductListPage } from "../../src/screens/product/list/ProductListPage"; |
| 51 | +
|
| 52 | +const ProductsPage = (): JSX.Element => ( |
| 53 | + <main> |
| 54 | + <Shell> |
| 55 | + <Layout> |
| 56 | + <ProductListPage /> |
| 57 | + </Layout> |
| 58 | + </Shell> |
| 59 | + </main> |
| 60 | +); |
| 61 | +
|
| 62 | +export default ProductsPage;`}</code></pre> |
| 63 | + |
| 64 | + <Alert severity="info" sx={{my: 2}}> |
| 65 | + Because all real code lives in <code>src/</code>, migrating from Next.js to Remix, |
| 66 | + Vite, or any other framework means changing only the <code>app/</code> folder. |
| 67 | + Everything in <code>src/</code> stays untouched. |
| 68 | + </Alert> |
| 69 | + |
| 70 | + <h2>src/ — All Application Code</h2> |
| 71 | + <p> |
| 72 | + The <code>src/</code> folder is structured by <strong>concern</strong>, not by feature. |
| 73 | + Each sub-folder has a single, well-defined responsibility. |
| 74 | + </p> |
| 75 | + |
| 76 | + <h3>pages/ (or screens/)</h3> |
| 77 | + <p> |
| 78 | + Full-screen page components. Each page corresponds to a route and composes |
| 79 | + multiple smaller components. Pages own their own store, styles, and tests — |
| 80 | + exactly like regular components (see <strong>components/</strong> below). |
| 81 | + </p> |
| 82 | + <Alert severity="warning" sx={{my: 2}}> |
| 83 | + In Next.js the name <code>pages/</code> is reserved for the Pages Router. |
| 84 | + If you use the App Router, name this folder <code>screens/</code> to avoid |
| 85 | + conflicts — the purpose and structure are identical. |
| 86 | + </Alert> |
| 87 | + <pre><code>{`src/screens/ |
| 88 | +├── product/ |
| 89 | +│ ├── list/ |
| 90 | +│ │ ├── ProductListPage.tsx |
| 91 | +│ │ ├── ProductListPage.store.ts |
| 92 | +│ │ ├── ProductListPage.styles.ts |
| 93 | +│ │ ├── ProductListPage.unit.spec.ts |
| 94 | +│ │ └── components/ ← page-local sub-components |
| 95 | +│ └── card/ |
| 96 | +│ └── ... |
| 97 | +└── order/ |
| 98 | + └── list/ |
| 99 | + └── ...`}</code></pre> |
| 100 | + |
| 101 | + <h3>components/</h3> |
| 102 | + <p> |
| 103 | + Reusable UI components shared across multiple pages. Every component is a |
| 104 | + <strong> self-contained folder</strong> with five files: |
| 105 | + </p> |
| 106 | + <pre><code>{`src/components/header/ |
| 107 | +├── Header.tsx ← React component (view only) |
| 108 | +├── Header.store.ts ← ReCA store with all state and logic |
| 109 | +├── Header.styles.tsx ← styled-components / CSS-in-JS styles |
| 110 | +├── Header.spec.ts ← unit tests (Jest / Mocha) |
| 111 | +└── Header.md ← component documentation`}</code></pre> |
| 112 | + <p> |
| 113 | + This co-location rule means you always know exactly where to look. Renaming or |
| 114 | + deleting a component is safe — all its moving parts are in one place. |
| 115 | + </p> |
| 116 | + <pre><code>{`// Header.tsx — thin view, delegates everything to the store |
| 117 | +import { useStore } from "reca"; |
| 118 | +import { HeaderStore } from "./Header.store"; |
| 119 | +import { HeaderRoot } from "./Header.styles"; |
| 120 | +
|
| 121 | +export const Header = () => { |
| 122 | + const store = useStore(HeaderStore); |
| 123 | +
|
| 124 | + return ( |
| 125 | + <HeaderRoot> |
| 126 | + <span>{store.userName}</span> |
| 127 | + <button onClick={() => store.signOut()}>Sign out</button> |
| 128 | + </HeaderRoot> |
| 129 | + ); |
| 130 | +};`}</code></pre> |
| 131 | + |
| 132 | + <h3>services/</h3> |
| 133 | + <p> |
| 134 | + Services contain business logic. They are split into <strong>four sub-folders</strong> |
| 135 | + that mirror the classic Clean Architecture layers: |
| 136 | + </p> |
| 137 | + <pre><code>{`src/services/ |
| 138 | +├── domain-services/ ← pure business rules, no side-effects |
| 139 | +├── app-services/ ← orchestration: coordinate domain services + repositories |
| 140 | +├── infr-services/ ← infrastructure: analytics, logging, monitoring |
| 141 | +└── view-services/ ← UI-level services: toasts, modals, navigation`}</code></pre> |
| 142 | + <ul> |
| 143 | + <li> |
| 144 | + <strong>domain-services/</strong> — encapsulate core business rules. |
| 145 | + They know nothing about HTTP, the DOM, or React. Example:{" "} |
| 146 | + <code>PricingService</code> calculates discounts based on customer tier. |
| 147 | + </li> |
| 148 | + <li> |
| 149 | + <strong>app-services/</strong> — application use-cases. They orchestrate |
| 150 | + domain services and repositories to fulfil a user action. Example:{" "} |
| 151 | + <code>OrderService</code> fetches orders, applies filters, and formats |
| 152 | + them for display. |
| 153 | + </li> |
| 154 | + <li> |
| 155 | + <strong>infr-services/</strong> — infrastructure concerns: error tracking, |
| 156 | + analytics events, OpenTelemetry spans. They are injected like any other |
| 157 | + service but wrap third-party SDKs. |
| 158 | + </li> |
| 159 | + <li> |
| 160 | + <strong>view-services/</strong> — services that talk to the UI layer:{" "} |
| 161 | + <code>ToastService</code> shows notifications,{" "} |
| 162 | + <code>NotificationService</code> manages the notification centre. |
| 163 | + They are singletons injected into stores that need to trigger UI feedback. |
| 164 | + </li> |
| 165 | + </ul> |
| 166 | + |
| 167 | + <Alert severity="info" sx={{my: 2}}> |
| 168 | + The dependency rule flows in one direction:{" "} |
| 169 | + <code>view-services</code> → <code>app-services</code> → <code>domain-services</code>. |
| 170 | + Infrastructure services can be injected at any layer. Repositories are only |
| 171 | + called from <code>app-services</code>. |
| 172 | + </Alert> |
| 173 | + |
| 174 | + <h3>repositories/</h3> |
| 175 | + <p> |
| 176 | + Data-access layer. Each repository is responsible for one API resource and |
| 177 | + wraps all HTTP calls for that resource. Stores and services never call{" "} |
| 178 | + <code>fetch</code> directly — they always go through a repository. |
| 179 | + </p> |
| 180 | + <pre><code>{`src/repositories/ |
| 181 | +├── ProductRepository.ts ← GET /products, POST /products, etc. |
| 182 | +├── OrderRepository.ts |
| 183 | +├── UserRepository.ts |
| 184 | +└── base/ |
| 185 | + └── BaseRepository.ts ← shared fetch logic, auth headers, error handling`}</code></pre> |
| 186 | + |
| 187 | + <h3>models/</h3> |
| 188 | + <p> |
| 189 | + TypeScript interfaces and types that describe the domain data. Models are plain |
| 190 | + data shapes — no logic, no dependencies: |
| 191 | + </p> |
| 192 | + <pre><code>{`src/models/ |
| 193 | +├── Product.ts ← interface IProduct { id: string; title: string; ... } |
| 194 | +├── Order.ts |
| 195 | +└── User.ts`}</code></pre> |
| 196 | + |
| 197 | + <h3>hooks/</h3> |
| 198 | + <p> |
| 199 | + Custom React hooks that encapsulate UI logic which doesn't belong to a store. |
| 200 | + Typical examples: <code>useDebounce</code>, <code>useMediaQuery</code>,{" "} |
| 201 | + <code>useOutsideClick</code>. |
| 202 | + </p> |
| 203 | + |
| 204 | + <h3>helpers/</h3> |
| 205 | + <p> |
| 206 | + Domain-aware pure functions with no side-effects: format a price, build a |
| 207 | + breadcrumb path, map an API response to a view model. Helpers know about your |
| 208 | + domain but have no external dependencies. |
| 209 | + </p> |
| 210 | + |
| 211 | + <h3>utils/</h3> |
| 212 | + <p> |
| 213 | + Generic utilities with no domain knowledge: deep clone, chunk an array, |
| 214 | + debounce a function. These could be extracted into a separate package without |
| 215 | + any changes. |
| 216 | + </p> |
| 217 | + |
| 218 | + <h3>validations/</h3> |
| 219 | + <p> |
| 220 | + Form validation schemas (Zod, Yup, or hand-written). Kept here so they can |
| 221 | + be reused across pages and tested in isolation. |
| 222 | + </p> |
| 223 | + |
| 224 | + <h3>localization/</h3> |
| 225 | + <p> |
| 226 | + Translation files and the i18n configuration. Components import keys, never |
| 227 | + raw strings. |
| 228 | + </p> |
| 229 | + |
| 230 | + <h3>styles/</h3> |
| 231 | + <p> |
| 232 | + Global CSS variables, theme tokens, and shared styled-components. Import |
| 233 | + from here instead of duplicating values across component files. |
| 234 | + </p> |
| 235 | + |
| 236 | + <h3>icons/</h3> |
| 237 | + <p> |
| 238 | + SVG icon components. Centralising them here avoids scattered inline SVGs and |
| 239 | + makes it easy to swap the icon set. |
| 240 | + </p> |
| 241 | + |
| 242 | + <h2>Why Framework Independence Matters</h2> |
| 243 | + <p> |
| 244 | + Because <code>app/</code> is only routing glue, the entire <code>src/</code>{" "} |
| 245 | + folder can be moved to a different framework without touching a single store |
| 246 | + or service. For example, switching from Next.js App Router to Pages Router |
| 247 | + means rewriting only the files in <code>app/</code>: |
| 248 | + </p> |
| 249 | + <pre><code>{`// Before: app/products/page.tsx (App Router) |
| 250 | +import { ProductListPage } from "../../src/screens/product/list/ProductListPage"; |
| 251 | +export default function Page() { return <ProductListPage />; } |
| 252 | +
|
| 253 | +// After: pages/products.tsx (Pages Router) |
| 254 | +import { ProductListPage } from "../src/screens/product/list/ProductListPage"; |
| 255 | +export default function Page() { return <ProductListPage />; } |
| 256 | +
|
| 257 | +// src/ is completely unchanged`}</code></pre> |
| 258 | + |
| 259 | + <h2>Summary</h2> |
| 260 | + <ul> |
| 261 | + <li><code>app/</code> — Next.js routing only; thin wrappers that delegate to <code>src/</code></li> |
| 262 | + <li><code>src/screens/</code> — full-screen page components with co-located store, styles, and tests (use <code>screens/</code> instead of <code>pages/</code> to avoid the Next.js naming conflict)</li> |
| 263 | + <li><code>src/components/</code> — reusable components; each is a self-contained folder with view, store, styles, tests, and docs</li> |
| 264 | + <li><code>src/services/</code> — business logic in four layers: domain, app, infrastructure, view</li> |
| 265 | + <li><code>src/repositories/</code> — all HTTP/data-access code; never called directly from components</li> |
| 266 | + <li><code>src/models/</code> — pure TypeScript interfaces; no logic</li> |
| 267 | + <li><code>src/hooks/</code>, <code>helpers/</code>, <code>utils/</code>, <code>validations/</code>, <code>localization/</code> — supporting utilities, each with a clear scope</li> |
| 268 | + </ul> |
| 269 | + </DocContent> |
| 270 | +); |
0 commit comments