# Architecture **Analysis Date:** 2026-04-18 ## Pattern Overview **Overall:** Server-first Next.js App Router monolith with localized page routes, thin API handlers, Prisma-backed data access, and small client-side interaction islands. **Key Characteristics:** - Use `src/app/layout.tsx` and `src/app/[locale]/layout.tsx` as the primary composition roots, with most route files implemented as async server components. - Keep browser-only interaction in explicit client components such as `src/app/[locale]/projects/ProjectsPageClient.tsx`, `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/components/search/AISearchBar.tsx`, and `src/components/signals/SignalFeedClient.tsx`. - Centralize most project and home-page reads in `src/hooks/useProjects.ts` and `src/hooks/useHome.ts`, even though these modules are named like React hooks. - Validate external input with Zod schemas from `src/lib/validations.ts` before querying or mutating the database. - Use Prisma as the only persistence client through `src/lib/prisma.ts`, with schema ownership in `prisma/schema.prisma`. ## Layers **Routing and Layout Layer:** - Purpose: Resolve locale-aware routes, shared layout chrome, SEO metadata, and global CSS. - Location: `src/app/layout.tsx`, `src/app/[locale]/layout.tsx`, `src/app/[locale]/page.tsx`, `src/app/[locale]/projects/page.tsx`, `src/app/[locale]/projects/[id]/page.tsx`, `src/app/[locale]/signals/page.tsx`, `src/app/[locale]/about/page.tsx`, `src/app/sitemap.ts`, `src/app/robots.ts`, `src/middleware.ts`, `src/i18n/request.ts` - Contains: App Router entry points, `generateMetadata`, `generateStaticParams`, `revalidate` exports, locale middleware, and SEO documents. - Depends on: `next-intl`, server query modules in `src/hooks`, UI components in `src/components`, and shared helpers in `src/lib`. - Used by: The Next.js runtime. **UI Composition Layer:** - Purpose: Render reusable view fragments for pages and route-specific UI sections. - Location: `src/components/home/*`, `src/components/layout/*`, `src/components/locale/*`, `src/components/project/*`, `src/components/search/*`, `src/components/signals/*` - Contains: Mostly presentational components, plus a few server components that call translations or helpers directly, such as `src/components/project/ProjectList.tsx` and `src/components/project/ProjectSidebar.tsx`. - Depends on: Translation APIs, route props, and typed data returned by `src/hooks/useProjects.ts` or `src/hooks/useHome.ts`. - Used by: Route entry points under `src/app/[locale]`. **Client Interaction Layer:** - Purpose: Own browser-only state, URL mutation, fetch loops, local storage, and interactive filters/search. - Location: `src/app/VercelMetrics.tsx`, `src/app/[locale]/projects/ProjectsPageClient.tsx`, `src/app/[locale]/projects/ProjectsResultsClient.tsx`, `src/components/layout/AnnouncementBar.tsx`, `src/components/locale/LocaleSwitcher.tsx`, `src/components/project/TagFilterPanel.tsx`, `src/components/search/AISearchBar.tsx`, `src/components/search/HomeSearchBar.tsx`, `src/components/search/AISearchResults.tsx`, `src/components/signals/SignalFeedClient.tsx` - Contains: `useState`/`useEffect`-driven UI state, `window.history.replaceState`, `window.location.href`, `localStorage`, and client-side `fetch` calls to `/api/*`. - Depends on: Page props, Next navigation APIs, and JSON APIs implemented under `src/app/api`. - Used by: Server-rendered pages and layouts. **Query and Read Model Layer:** - Purpose: Encapsulate database reads, filtering, retry logic, shape transformations, and cached read models for projects, tags, and home-page aggregates. - Location: `src/hooks/useProjects.ts`, `src/hooks/useHome.ts` - Contains: Prisma queries, pagination logic, tag-category grouping, home-page aggregate builders, and result-type definitions such as `ProjectWithFlatTags`, `FilterTagCategoryGroup`, and `HomePageData`. - Depends on: `src/lib/prisma.ts`, `src/lib/cache.ts`, `src/lib/tag-taxonomy.ts`, and Prisma types from `@prisma/client`. - Used by: Page routes such as `src/app/[locale]/page.tsx` and list APIs such as `src/app/api/projects/route.ts`. **API and Mutation Layer:** - Purpose: Expose JSON endpoints for list/detail reads, AI search proxying, signals feed reads, authenticated tag maintenance, project-tag resets, and signal ingestion. - Location: `src/app/api/projects/route.ts`, `src/app/api/projects/[slug]/route.ts`, `src/app/api/search/ai/route.ts`, `src/app/api/signals/route.ts`, `src/app/api/tags/route.ts`, `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/maintenance/service.ts`, `src/app/api/tags/reset-projects/route.ts`, `src/app/api/webhook/signals/route.ts` - Contains: `GET` and `POST` route handlers, request parsing, validation, response shaping, cache revalidation, and transaction-scoped maintenance logic. - Depends on: `src/lib/validations.ts`, `src/lib/auth.ts`, `src/lib/prisma.ts`, `src/lib/signal-hotness.ts`, and the query layer in `src/hooks`. - Used by: Client components, external webhook callers, and automation clients. **Domain Utility Layer:** - Purpose: Hold cross-route domain logic that is not tied to a single route. - Location: `src/lib/auth.ts`, `src/lib/cache.ts`, `src/lib/prisma.ts`, `src/lib/signal-hotness.ts`, `src/lib/slug.ts`, `src/lib/tag-taxonomy.ts`, `src/lib/validations.ts`, `src/lib/github/badges.ts` - Contains: API-key verification, cache fallback helpers, Prisma singleton setup, signal hotness scoring, slug generation, tag taxonomy inference, schema validation, and GitHub link parsing. - Depends on: Standard library APIs, Prisma, and Zod. - Used by: Both pages and API handlers. **Persistence Layer:** - Purpose: Define and migrate the database model and seed baseline data. - Location: `prisma/schema.prisma`, `prisma/migrations/*`, `prisma/seed.ts` - Contains: PostgreSQL schema for `Project`, `Tag`, `ProjectTag`, `ExternalLink`, and `Signal`, plus migrations and seed data. - Depends on: Prisma CLI and environment-provided `DATABASE_URL`. - Used by: `src/lib/prisma.ts` at runtime and Prisma tooling during migrations/seed. ## Data Flow **Localized Page Render:** 1. `src/middleware.ts` forces a locale-prefixed pathname and excludes `/api`, `/_next`, and static assets from locale routing. 2. `src/app/[locale]/layout.tsx` validates the locale, calls `setRequestLocale(locale)`, loads messages through `next-intl`, and wraps the tree with `NextIntlClientProvider`. 3. Route pages such as `src/app/[locale]/page.tsx`, `src/app/[locale]/projects/page.tsx`, and `src/app/[locale]/projects/[id]/page.tsx` fetch data from `src/hooks/useProjects.ts` or `src/hooks/useHome.ts`. 4. Reusable server and client components render the data, with ISR enabled through `export const revalidate = 300` on the main content routes. **Traditional Projects Browse Flow:** 1. `src/app/[locale]/projects/page.tsx` reads `searchParams`, normalizes filters, and calls `getProjects`, `getFixedProjectTypeFilters`, and `getTagCategoryGroups` from `src/hooks/useProjects.ts`. 2. `src/app/[locale]/projects/ProjectsPageClient.tsx` and `src/components/project/TagFilterPanel.tsx` manage filter-panel visibility and query-string updates in the browser. 3. `src/app/[locale]/projects/ProjectsResultsClient.tsx` fetches updated pages from `src/app/api/projects/route.ts` for client-side pagination and sorting. 4. `src/app/api/projects/route.ts` validates query parameters with Zod, then delegates to `getProjects`. **AI Search Flow:** 1. `src/components/search/AISearchBar.tsx` toggles between traditional and AI mode and forwards the query back to the page-level handler. 2. `src/app/[locale]/projects/ProjectsResultsClient.tsx` requests `POST /api/search/ai`. 3. `src/app/api/search/ai/route.ts` validates the request with `ProjectQuerySchema`, forwards a GET request to the external N8N webhook URL from `N8N_AI_SEARCH_WEBHOOK`, then hydrates returned IDs by calling `getProjectsByIds` from `src/hooks/useProjects.ts`. 4. The route sorts and filters the hydrated projects again before returning JSON to the client. **Signals Feed Flow:** 1. `src/app/[locale]/signals/page.tsx` renders shell content and mounts `src/components/signals/SignalFeedClient.tsx`. 2. `SignalFeedClient` holds search text, debounce state, source filter, sort mode, and pagination cursor in component state. 3. The client calls `GET /api/signals` on `src/app/api/signals/route.ts`. 4. `src/app/api/signals/route.ts` validates the query, queries Prisma, computes or reads hotness metadata via `src/lib/signal-hotness.ts`, and returns cursor-based pagination. **Signal Ingestion Flow:** 1. External automation posts to `src/app/api/webhook/signals/route.ts`. 2. The handler validates the top-level payload and each item with `SignalWebhookPayloadSchema` and `SignalIngestionInputSchema` from `src/lib/validations.ts`. 3. `src/lib/auth.ts` verifies `apiKey` using `crypto.timingSafeEqual`. 4. The handler upserts signals in Prisma, computing fallback hotness when needed and downgrading if `hotScore`/`isHot` columns are absent. **Tag Maintenance Flow:** 1. Clients call `POST /api/tags/maintenance` or `POST /api/tags/reset-projects`. 2. The route validates input, authenticates with `isValidApiKey`, and runs Prisma mutations. 3. `src/app/api/tags/maintenance/route.ts` delegates transactional merge/update logic to `src/app/api/tags/maintenance/service.ts`. 4. Successful mutations call `revalidatePath` for affected localized pages. ## State Management **Server State:** - Persistent application state lives in PostgreSQL, modeled in `prisma/schema.prisma` and accessed only through Prisma in `src/lib/prisma.ts`. - Page-level read models are built in `src/hooks/useProjects.ts` and `src/hooks/useHome.ts`. **Cached Server State:** - `unstable_cache` is used in `src/hooks/useProjects.ts`, `src/hooks/useHome.ts`, and `src/app/api/tags/route.ts`. - `src/lib/cache.ts` provides `runWithCacheFallback` so cached fetchers can fall back to direct database reads when Incremental Cache is unavailable. **Client UI State:** - Local component state, not a global store, drives interactivity. - `src/app/[locale]/projects/ProjectsPageClient.tsx` stores filter-panel expansion. - `src/app/[locale]/projects/ProjectsResultsClient.tsx` stores AI/traditional pagination, loading flags, and sort/limit state. - `src/components/signals/SignalFeedClient.tsx` stores debounced search, source filters, sort mode, cursor, and loading state. - `src/components/layout/AnnouncementBar.tsx` stores dismissal state in `localStorage`. **URL State:** - Search, pagination, sort, AI mode, and taxonomy filters are encoded in the query string on `src/app/[locale]/projects/page.tsx`. - The projects UI treats the URL as the source of truth and synchronizes local state to it through `router.push` and `window.history.replaceState`. **Translation State:** - Locale selection is path-based and enforced by `src/middleware.ts`. - Message bundles load from `src/messages/en.json` and `src/messages/zh.json` via `src/i18n/request.ts`. ## Key Abstractions **Project Read Model:** - Purpose: Present projects with flattened tags and related links, independent of Prisma join-table shape. - Examples: `ProjectWithFlatTags` and `getProjects` in `src/hooks/useProjects.ts`, `getProjectBySlug` in `src/hooks/useProjects.ts` - Pattern: Read-model transformation from Prisma includes to page/API-friendly objects. **Tag Taxonomy:** - Purpose: Normalize and classify project tags into fixed categories used by filters and maintenance APIs. - Examples: `src/lib/tag-taxonomy.ts`, `getTagCategoryGroups` in `src/hooks/useProjects.ts` - Pattern: Central domain vocabulary with inference helpers shared across pages and mutations. **Signal View Model:** - Purpose: Convert raw `Signal` rows, localized text fields, JSON sections, and hotness metadata into feed-ready objects. - Examples: `toSignalView` and `parseSections` in `src/app/api/signals/route.ts` - Pattern: API-specific presentation mapping on top of Prisma rows. **Maintenance Service:** - Purpose: Keep complex tag merge/update rules out of the route handler. - Examples: `executeTagMaintenance` and `TagMaintenanceApiError` in `src/app/api/tags/maintenance/service.ts` - Pattern: Route delegates orchestration to a transaction-aware service module. **Auth Guard for Internal APIs:** - Purpose: Reuse constant-time API-key validation across maintenance and webhook routes. - Examples: `isValidApiKey` in `src/lib/auth.ts`, used by `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`, and `src/app/api/webhook/signals/route.ts` - Pattern: Small shared guard function, not middleware-based auth. ## Entry Points **Root App Shell:** - Location: `src/app/layout.tsx` - Triggers: Every page render. - Responsibilities: Global HTML/body shell, global CSS import, and conditional Vercel analytics injection through `src/app/VercelMetrics.tsx`. **Localized App Shell:** - Location: `src/app/[locale]/layout.tsx` - Triggers: Every localized page render under `//*`. - Responsibilities: Locale validation, translations, site navigation, announcement bar, footer, and `NextIntlClientProvider`. **Home Route:** - Location: `src/app/[locale]/page.tsx` - Triggers: `GET /zh` and `GET /en` - Responsibilities: Render home hero, stats, rankings, tag insights, recent timeline, and featured projects. **Projects Route:** - Location: `src/app/[locale]/projects/page.tsx` - Triggers: `GET //projects` - Responsibilities: Parse URL filters, render filter/search UI, fetch paginated project data, and mount the projects results client island. **Project Detail Route:** - Location: `src/app/[locale]/projects/[id]/page.tsx` - Triggers: `GET //projects/:slug` - Responsibilities: Fetch a single project, derive related projects, render sidebar/details, and generate route metadata. **Signals Route:** - Location: `src/app/[locale]/signals/page.tsx` - Triggers: `GET //signals` - Responsibilities: Render the signals feed shell and mount the client-side feed loader. **API Routes:** - Location: `src/app/api/*` - Triggers: Browser fetches and external webhook clients. - Responsibilities: JSON read endpoints, AI search proxying, signals feed pagination, authenticated tag maintenance, project tag resets, and signal ingestion. **Locale Middleware:** - Location: `src/middleware.ts` - Triggers: All non-API, non-static requests. - Responsibilities: Apply the `next-intl` locale prefix policy and route matching. ## Error Handling **Strategy:** Validate early, return structured JSON for API errors, use `notFound()` for invalid page resources, and degrade some read paths to empty or fallback results instead of hard-failing the page. **Patterns:** - Use Zod validation in `src/app/api/projects/route.ts`, `src/app/api/search/ai/route.ts`, `src/app/api/signals/route.ts`, `src/app/api/tags/maintenance/route.ts`, `src/app/api/tags/reset-projects/route.ts`, and `src/app/api/webhook/signals/route.ts`. - Use `notFound()` in `src/app/[locale]/layout.tsx`, `src/app/[locale]/projects/[id]/page.tsx`, and `src/app/[locale]/[...catchAll]/page.tsx` for route-level misses. - Retry transient database errors in `src/hooks/useProjects.ts` via `withDbRetry`. - Degrade to empty or zero-count results in `src/hooks/useProjects.ts` and `src/hooks/useHome.ts` when some DB reads fail. - Wrap service-specific failures in `TagMaintenanceApiError` in `src/app/api/tags/maintenance/service.ts`. ## Cross-Cutting Concerns **Logging:** Use `console.error` and `console.warn` directly in route handlers and server query modules, for example in `src/app/api/search/ai/route.ts`, `src/app/api/signals/route.ts`, `src/app/api/webhook/signals/route.ts`, `src/hooks/useProjects.ts`, and `src/hooks/useHome.ts`. **Validation:** Use shared Zod schemas from `src/lib/validations.ts` for external request bodies and query parameters. **Authentication:** Protect internal mutation/webhook endpoints with API-key checks from `src/lib/auth.ts`. No session or user-account auth layer is present in the inspected files. **Internationalization:** Use `next-intl` with locale-prefixed routing through `src/middleware.ts`, request config in `src/i18n/request.ts`, and message bundles in `src/messages/*.json`. **Caching and Revalidation:** Use ISR-style route revalidation (`revalidate = 300`), `unstable_cache`, and `revalidatePath` after tag mutations. **Observability:** The only built-in runtime instrumentation found is Vercel analytics in `src/app/VercelMetrics.tsx`. No separate tracing or background job framework was detected in inspected files. --- *Architecture analysis: 2026-04-18*