Stack
- React Router v7 (formerly Remix) with SSR on Cloudflare Workers
- Vite with
@cloudflare/vite-pluginfor local dev and build - Tailwind CSS v4 via
@tailwindcss/vite - shadcn/ui components (Card, Button, Input, Separator, Sidebar, Sheet, Table, Tooltip, Breadcrumb)
- lucide-react for icons
Route structure
/ Dashboard index (_layout._index.tsx)
/live Live event feed (_layout.live.tsx)
/trails Public trail browser (_layout.trails.tsx)
/my/trails Authenticated user's cloud trails (_layout.my.trails.tsx)
/my/settings Account settings with passkey management (_layout.my.settings.tsx)
/audit Audit log viewer (_layout.audit.tsx)
/extension-test Extension integration test page (_layout.extension-test.tsx)
/about Platform info page (_layout.about.tsx)
/login Login page (standalone, no sidebar)
/register Registration page (standalone, no sidebar)
/trail/:id Single trail viewer (standalone, no sidebar)
Routes inside _layout.tsx get the sidebar + header shell. Login, register, and single-trail pages render standalone.
Layout shell
_layout.tsx wraps content with:
SidebarProviderfrom shadcn/uiAppSidebar— collapsible sidebar with navigation groupsDashboardHeader— top header barSidebarInset— main content area withp-6padding
Sidebar navigation (app-sidebar.tsx)
Five navigation groups:
- Overview — Dashboard, Live Feed
- History — Trails, Audit Log
- Debug — Extension Test
- Account — My Trails, Settings
- Info — About
Active state is determined by useLocation().pathname prefix matching.
Authentication pages
Login (/login) — Card-based form with:
- "Sign in with passkey" button (shown only if
browserSupportsWebAuthn()returns true) - "or" divider
- Email + password form
- Email verification flow (inline, shows 6-digit code input if
requiresVerificationis returned) - Resend code link
- On success: sets
blincr_tokencookie (30 days, SameSite=Lax) and navigates to/my/trails
Passkey login flow: calls POST /api/auth/passkey/login-options, triggers startAuthentication() from @simplewebauthn/browser, sends result to POST /api/auth/passkey/login-verify.
Register (/register) — Two-step form:
- Name, email, password (min 6 chars), terms checkbox
- 6-digit verification code input (after registration succeeds)
Both pages use the blincr logo, brand name, and Mondial-IT tagline in the card header.
Account settings (/my/settings)
Passkey management page:
- Lists registered passkeys with device name and creation date
- "Add passkey" form with optional device name input
- Delete button per passkey (ghost variant, destructive color)
- Browser support detection — shows fallback message if WebAuthn unavailable
- Success/error feedback messages
Registration flow: calls POST /api/auth/passkey/register-options, triggers startRegistration(), sends result to POST /api/auth/passkey/register-verify.
Key dependencies
@simplewebauthn/browser ^11.0.0 Client-side WebAuthn API wrapper
@simplewebauthn/server ^11.0.0 Server-side attestation/assertion verification
react ^19.1.0 UI framework
react-dom ^19.1.0 DOM renderer
react-router ^7.6.2 Full-stack routing with SSR
isbot ^5.1.43 Bot detection for SSR
Dev dependencies: @cloudflare/vite-plugin, @cloudflare/workers-types, @react-router/dev, @tailwindcss/vite, class-variance-authority, clsx, lucide-react, tailwind-merge, tailwindcss, typescript, vite, wrangler.
Development
npm run dev Start Vite dev server with Cloudflare Workers runtime
npm run build Build client + server bundles
npm run preview Build then serve with wrangler dev
npm run deploy Build then deploy to Cloudflare
npm run typecheck Generate types + run tsc
Local D1 is stored in .wrangler/state/v3/d1/. Run migrations before first dev session.