Back to docs hub·docs/architecture/24-frontend-structure.md
Reference document

24 Frontend App Structure

Last updated Mar 23, 2026

24 Frontend App Structure

Purpose of this page

Define a frontend structure that:

  • enables premium app-like UX (fast navigation, responsive search),
  • supports offline-friendly caching (IndexedDB snapshot),
  • stays maintainable as screens grow,
  • and keeps us portable (no dependence on backend rendering or BaaS client SDKs).
  • We're using Next.js + TypeScript + Tailwind + shadcn/ui because:

Next.js is the most proven web app framework for production scale,

React ecosystem gives us mature UI patterns,

and shadcn/ui gives us accessible components we own in our repo (full control).

High-level frontend architecture

Core idea

The frontend is a PWA client that talks to our NestJS API. It keeps a local snapshot of inventory in IndexedDB to make:

  • search instant,
  • cellar/offline browsing possible,
  • and UI feel “native”.

Separation of concerns

UI components: reusable and dumb (no direct API calls)

Data layer: API client + cache + sync logic

Screens/routes: compose components + call data layer

This separation is what keeps a Next.js codebase scalable.

Route map (MVP)

We structure routes to match screens in Page 06.

Auth & onboarding

/login /register /onboarding

Main app (authenticated)

/dashboard → Dashboard (Search-first home) /locations /locations/[id] → Location Detail /items/[id] → Item Detail /items/new → Add Item /items/[id]/edit → Edit Item /items/[id]/move → Move picker (or modal route)

Settings

/settings /settings/import → CSV bulk import wizard (feature-gated: PRO+) /settings/team → Team member management (feature-gated: TEAM) /settings/subscription → Plan management

Why routes vs heavy modal-only navigation

Routes:

  • preserve browser history (Back button works),
  • allow deep linking,
  • and improve debugging/testing.
  • We can still show certain screens as modals while maintaining real routes.

Layout structure

App shell

AppLayout for authenticated routes:

  • top bar (contextual)
  • content
  • bottom tab nav

Auth layout

minimal centered layout for login/register

Why

A consistent shell makes PWA feel like a native app and reduces repeated code.

State and data management (how we keep it simple but scalable)

Data sources

  1. API (source of truth)
  2. IndexedDB cache (offline + speed)

Data layer modules

We'll implement a small set of modules rather than adopting a heavy global state framework prematurely.

Recommended structure

1frontend/

2src/

3app/ (Next.js app router)

4components/

5features/

Why this over Redux from day 1

Redux is great but adds ceremony. For our MVP:

  • most state is server-derived,
  • we can use local component state + lightweight caching,
  • then add a state library later if needed.

API client conventions

lib/api/client.ts wraps fetch includes:

  • credentials: "include" (cookies)
  • base URL
  • standardized error handling (Page 16 error shape)

Why

Consistency prevents “random fetch calls everywhere” and makes debugging far easier.

IndexedDB (Dexie) caching plan (MVP)

What we cache

locations (flat) items (flat) optionally precomputed breadcrumbs for items

When we write cache

after successful sync calls (online) after writes (create/edit/move/delete) succeed Only for accounts whose current plan includes offline mode. If a user loses that entitlement, the client should clear the local inventory snapshot instead of continuing to serve stale offline data.

When we read cache

on app start to populate UI immediately when offline 6. auth/ 7. items/ 8. locations/ 9. search/

10lib/

11 api/

12 client.ts

13 endpoints.ts

14 types.ts

15 cache/

16 db.ts (Dexie schema)

17 sync.ts

18 auth/

19 session.ts

20 utils/

21 debounce.ts

Why this improves UX

instant search even with slow network usable in cellars without signal fast UI = higher trust

Search architecture (critical)

Online-first, offline fallback

If online:

  • call /search?q=... for canonical results
  • update local cache optionally
  • If offline:
  • if the signed-in plan includes offline mode, search locally in IndexedDB snapshot
  • otherwise show an offline-unavailable recovery state instead of cached results
  • show “Offline — results may be stale” banner

Why not always local search

Local search is fast, but online search can incorporate:

  • server ranking improvements,
  • future features (synonyms, typo tolerance),
  • and ensures consistency across devices.

UI composition rules (how screens are built)

Screen components

Each route is a screen that composes reusable components. Example:

Dashboard (Search-first home)

SearchBar

FavoritesList (from API)

SearchResultsList (ItemRow list)

EmptyState

Component ownership

components/ holds generic UI (Button wrappers, BreadcrumbPath, EmptyState) features/* holds domain components (ItemRow, LocationRow, TagInput, PhotoPicker, FavoritesList)

Why

This prevents a “components soup” and keeps domain logic grouped.

Authentication gating in the frontend

Strategy

Client-side route protection:

If /me fails → redirect to /login

Use a shared useSession() hook that:

  • fetches /me once,
  • caches user state,
  • triggers sync on login.

Why

Simple and effective for PWA clients with cookie auth.

Error and loading patterns (must-have)

Skeletons for lists

Inline error banners with Retry

Toast for “Saved/Moved/Deleted”

No full-screen spinners for core browsing/search

Why

These patterns directly affect perceived speed and trust.

PWA structure & caching

manifest + icons service worker caches the app shell data caching handled by IndexedDB, not service worker caching of API

Why

Service worker caching of authenticated API responses can create stale-data and security complexity. IndexedDB gives explicit control.

Decisions (locked for MVP)

Next.js routes map 1:1 to major screens (Back button friendly)

Shared AppLayout with bottom tab navigation

Central API client wrapper (credentials included, standard errors)

Dexie IndexedDB cache for locations/items snapshot

Search: online-first with offline fallback to local snapshot