Next.js codebase — overview
The dropafinder-app-nextjs repo is the customer-facing dashboard. It handles authentication, workspace management, finder configuration, the finder builder UI, billing, and admin tooling. It is a distinct application from the embeddable widget (dropafinder-app-external) and the Laravel API (dropafinder-app-backend).
Tech stack
| Layer | Technology | Version |
|---|---|---|
| Framework | Next.js (App Router) | 16.2.2 (package.json:14) |
| Language | TypeScript | ^5 (package.json:31) |
| UI rendering | React | 19.2.4 (package.json:17) |
| Client state | Zustand | ^5.0.12 (package.json:23) |
| Server state / data fetching | TanStack Query (React Query) | ^5.96.2 (package.json:13) |
| Styling | SCSS + $v2- design tokens | ^1.99.0 (package.json:20) |
| Map rendering | MapLibre GL + react-map-gl | ^5.22.0 / ^8.1.0 (package.json:16,21) |
| Billing | Paddle JS SDK | ^1.6.2 (package.json:11) |
| Theming | next-themes | ^0.4.6 (package.json:15) |
| QR code generation | qrcode.react | ^4.2.0 (package.json:18) |
| Utility | clsx + class-variance-authority | ^2.1.1 / ^0.7.1 (package.json:12) |
TypeScript strict mode is assumed throughout. All type-only imports use import type per AGENTS.md:36.
⚠️ Warning: Next.js 16 contains breaking changes relative to earlier versions. Before modifying framework-level code, read the relevant guide in
node_modules/next/dist/docs/. SeeAGENTS.md:3–4.
Route group architecture
The App Router tree under src/app/ is split into four distinct segments, each with its own root layout:
(auth)/
Login and registration flows. Two routes: (auth)/login and (auth)/register/[token]. The layout (src/app/(auth)/layout.tsx:6–18) renders a centred authCard wrapper around its children, mounts <AlertContainer />, and bootstraps the same <Providers> wrapper (QueryClient + ThemeProvider) as the app shell — meaning auth pages share query-client and theme infrastructure but not the dashboard chrome.
(marketing)/
Public marketing pages. The layout (src/app/(marketing)/layout.tsx:9–55) is a standalone <html>/<body> tree with no <Providers> wrapper and no shared SCSS imports. It loads the Gilroy typeface family (five weights, local woff/ttf) and DM Mono from Google Fonts. Kept isolated from the app bundle by design.
(preview)/
Internal preview and POC routes used during development — not customer-facing. Contains two routes: finder-builder-v3-poc (a sandbox for the v3 builder) and finder-preview. The layout (src/app/(preview)/layout.tsx:1–12) is a bare <html>/<body> with overflow: hidden — intended for full-viewport iframe-style rendering.
app/ (the dashboard)
The authenticated dashboard. Unlike the parenthesised groups above, app/ is a URL segment — all dashboard routes live under the /app/* path prefix. Its layout (src/app/app/layout.tsx:1–21) is the true root for authenticated users: it imports globals.scss, mounts <Providers>, and wraps everything in <AppShell> (the sidebar + top bar chrome).
Dashboard sub-routes:
| Route | Purpose |
|---|---|
app/dashboard | Home / overview |
app/finders | Finder list |
app/finders/[id]/[section] | Finder builder — section-based editing |
app/finders/[id]/analytics | Per-finder analytics |
app/locations | Location management |
app/sets | Sets |
app/maps | Maps |
app/tags | Tags |
app/custom-fields | Custom fields |
app/workspaces | Workspace switcher / management |
app/billing | Billing (Paddle) |
app/changelog | In-app changelog |
app/account | User account settings |
app/admin | Internal admin panel (role-gated) |
api/ (Next.js API routes)
Two API route families live inside the App Router:
api/ext/v2/snippet/— Serves the embeddable widget JavaScript. In development it attempts to read a local build fromdropafinder-app-external; if no local file is found it proxieshttps://cdn.locationfinders.com/snippet.js. This lets the preview tools load the widget without CORS restrictions (src/app/api/ext/v2/snippet/route.ts:1–44).api/map-style-preview/— Serves map style previews.
These are the only Next.js API routes. All application data calls go to the separate Laravel backend via fetchAPI / fetchExt.
Providers and bootstrap
src/providers/Providers.tsx is the single provider tree mounted by both (auth)/layout.tsx and app/layout.tsx. It composes:
ThemeProvider(next-themes) — reads/writesdata-themeattribute, defaults to system preference (Providers.tsx:21).QueryClientProvider(TanStack Query) — creates a per-render-treeQueryClientwith a 5-minutestaleTimeand a single retry (Providers.tsx:11–15).
Key conventions (source: AGENTS.md)
CSS — BEM only
All CSS class names follow BEM: block__element--modifier. New styles must use BEM. Pre-existing violations should be fixed in the same pass (AGENTS.md:13–22).
SCSS design tokens
Use $v2- prefixed SCSS variables for colors and var(--color-primary) style custom properties for component styles. Never hardcode hex values that map to an existing token (AGENTS.md:64–66). Token variables are defined in src/sass/_v2-tokens.scss.
API layer — no raw fetch
All API calls go through fetchAPI (authenticated calls to the backend REST API) or fetchExt (calls to the external/embed API) from src/lib/api/core.ts. Calling fetch() directly is prohibited (AGENTS.md:42–44). Domain files live in src/lib/api/ (e.g. finders.ts, locations.ts). New resource types require a normalizer in normalizers.ts and a payload transformer in payloads.ts.
The fetchAPI / fetchExt wrappers automatically:
- Attach the
Authorization: Bearer <token>header fromauthStore(core.ts:131–133) - Append
workspace_id=<activeWorkspaceId>viabuildWorkspaceAwareEndpointfor workspace-scoped requests (core.ts:24–35) - Emit audit headers (
X-Audit-Request-Id,X-Audit-Action,X-Audit-Resource-Type,X-Audit-Resource-Id) on mutating requests (core.ts:91–111) - Force logout and redirect to
/loginon 401 responses (core.ts:144–147)
Mutations and query invalidation
Write operations use useMutation from TanStack Query. The onSuccess callback must invalidate all relevant query keys — never leave a stale cache after a write (AGENTS.md:48–58). Query keys follow the [resource, id] pattern (e.g. ['finder', id], ['finders']).
Workspace scoping
Every request that is workspace-aware should pass through buildWorkspaceAwareEndpoint, which reads activeWorkspaceId from useWorkspaceStore and appends ?workspace_id=<id> to the endpoint string (core.ts:24–35). The workspace store persists its selection to localStorage under the key active-workspace (workspaceStore.ts:30).
Error surfacing
User-visible errors are surfaced via addAlert(message, 'error') from useUiStore. Do not use local component error state or console.error as a substitute for user-facing feedback (AGENTS.md:62).
Comments — why, not what
Comments explain the constraint or historical reason, not what the code does. Maximum three lines (AGENTS.md:26–32).
TypeScript imports
Type-only imports use import type. Object shapes use interface; unions and aliases use type (AGENTS.md:36–40).
Deployment
The app deploys to Vercel. No server-side rendering is used beyond the Next.js App Router defaults — the dashboard is effectively a client-rendered SPA behind an authentication gate. The marketing group ((marketing)/) is the only segment that would benefit from SSR for SEO purposes.
The embed snippet proxy route (api/ext/v2/snippet/) switches behaviour by NODE_ENV: in production it always proxies from CDN; in development it prefers a local build from the sibling dropafinder-app-external repo.
Environment variables are prefixed NEXT_PUBLIC_ for client-accessible values. NEXT_PUBLIC_API_URL is the only variable referenced directly in the source code reviewed (core.ts:10).
Related pages
- Directory structure — annotated
src/tree - Routing — full route inventory and middleware
- State management — Zustand stores + TanStack Query patterns ★
- Finder Builder v2 — section components, history reducer, dispatch pattern ★
- Architecture overview