These docs are a work in progress and may not be fully up to date. Some pages may contain internal notes for our team.
Skip to Content

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

LayerTechnologyVersion
FrameworkNext.js (App Router)16.2.2 (package.json:14)
LanguageTypeScript^5 (package.json:31)
UI renderingReact19.2.4 (package.json:17)
Client stateZustand^5.0.12 (package.json:23)
Server state / data fetchingTanStack Query (React Query)^5.96.2 (package.json:13)
StylingSCSS + $v2- design tokens^1.99.0 (package.json:20)
Map renderingMapLibre GL + react-map-gl^5.22.0 / ^8.1.0 (package.json:16,21)
BillingPaddle JS SDK^1.6.2 (package.json:11)
Themingnext-themes^0.4.6 (package.json:15)
QR code generationqrcode.react^4.2.0 (package.json:18)
Utilityclsx + 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/. See AGENTS.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:

RoutePurpose
app/dashboardHome / overview
app/findersFinder list
app/finders/[id]/[section]Finder builder — section-based editing
app/finders/[id]/analyticsPer-finder analytics
app/locationsLocation management
app/setsSets
app/mapsMaps
app/tagsTags
app/custom-fieldsCustom fields
app/workspacesWorkspace switcher / management
app/billingBilling (Paddle)
app/changelogIn-app changelog
app/accountUser account settings
app/adminInternal 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 from dropafinder-app-external; if no local file is found it proxies https://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:

  1. ThemeProvider (next-themes) — reads/writes data-theme attribute, defaults to system preference (Providers.tsx:21).
  2. QueryClientProvider (TanStack Query) — creates a per-render-tree QueryClient with a 5-minute staleTime and 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 from authStore (core.ts:131–133)
  • Append workspace_id=<activeWorkspaceId> via buildWorkspaceAwareEndpoint for 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 /login on 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).