Architecture
Rank Pilot AI SEO is a server-rendered React application built on TanStack Start, using TanStack Router for file-based routing and TanStack Query for client-side data fetching and caching. The app is served through a single fetch-style entry point and backed by a Supabase project (Postgres, Auth, Storage, Edge Functions) for persistence, background jobs and third-party integrations.
SSR entry point
src/server.ts is the runtime entry used in production. It lazily imports @tanstack/react-start/server-entry and delegates each incoming request to it. Two defensive layers sit around that call:
- Any error thrown synchronously during the SSR pass is caught and turned into a generic HTML error page (
src/lib/error-page.ts), rather than surfacing an unstyled crash. - TanStack Start's underlying HTTP layer (h3) can swallow in-handler exceptions and return a JSON
{ unhandled: true, message: "HTTPError" }body with a 5xx status.normalizeCatastrophicSsrResponsedetects that shape, logs the originally captured error (viasrc/lib/error-capture.ts), and rewrites the response as the same friendly error page.
src/router.tsx builds the client/server-shared router instance: it wires the generated routeTree (see below) to a fresh QueryClient per request/render, exposed to routes as router context, with scroll restoration enabled and no implicit stale-time on preloads.
Routing conventions (src/routes)
Routes are file-based and use flat, dot-separated filenames rather than nested folders, which TanStack Router's file-based route generator (routeTree.gen.ts, auto-generated — never hand-edited) expands into a nested route tree. Conventions in use:
index.tsx→/.login.tsx,signup.tsx,dashboard.tsx,billing.tsx, etc. → simple top-level pages.- Dot-segmented names express nesting/grouping without folders, e.g.
admin.users.tsx,admin.audit-logs.tsx,admin.cron-jobs.tsxall live under the/adminarea;blog.what-is-seo-software.tsxis a post under/blog. - Dynamic segments use
$param(e.g.admin.dev-agent.$threadId.tsx,invite.$token.tsx). - Bracketed filenames escape characters that would otherwise be parsed as route separators or special tokens —
[.]lovable.oauth.consent.tsxmaps to the literal path/.lovable/oauth/consent, used for the MCP OAuth consent screen. src/routes/api/public/holds routes that respond as plain HTTP endpoints (JSON/webhook-style) rather than rendering pages, for cases that need a stable public URL served directly by the app rather than a Supabase Edge Function.- Each route file exports a
Routecreated withcreateFileRoute(path)({ component }); page implementations mostly live insrc/pages/*and are imported into the thin route file, keeping routing declarations separate from page logic.
Server functions vs edge functions
Two distinct mechanisms provide server-side behaviour, and the choice between them is deliberate:
Server functions (*.functions.ts, e.g. src/lib/billing.functions.ts, src/lib/gscKeywords.functions.ts, src/lib/gscVerification.functions.ts, src/lib/supportReply.functions.ts) are defined with TanStack Start's createServerFn. They run inside the same SSR process as the app, are called like normal async functions from React components/loaders, and are the default choice for anything that needs to read the authenticated user's session and talk to Postgres directly. They commonly:
- attach a
requireSupabaseAuthmiddleware that resolves the caller's Supabase session/claims from the request cookies and injects a scoped Supabase client plususerIdintocontext; - validate their input with a
zodschema viainputValidator; - delegate the actual work to a paired
*.server.tsmodule (e.g.billing.server.ts,gscNightlySync.server.ts,mailchimp.server.ts) that contains the real implementation, keeping the callable wrapper thin.
Supabase Edge Functions (supabase/functions/*) are separate Deno deployments, invoked over HTTP (from the client, from other edge functions, or from Postgres cron/webhooks) rather than imported into the SSR bundle. They are used for work that:
- must run independently of a user request — scheduled jobs (
scheduled-rank-check,expire-trials,check-ranks), queue processors (autopilot-*), and cron-triggered maintenance; - calls third-party APIs that require server-side secrets never exposed to the SSR bundle (Stripe, Ahrefs, Google Ads, Twilio, Apify-backed scrapers, GSC/GA4 OAuth exchanges);
- handles inbound webhooks (
stripe-payment-webhook,crm-ticket-webhook); - needs to run with the Supabase service role to bypass RLS for privileged operations (admin tooling, cross-user aggregation), always behind an explicit role check.
A _shared/ folder under supabase/functions holds common helpers (Stripe/Twilio/Mailchimp/Google Ads clients, email templates, CORS handling for passkeys, promo-code validation) reused across functions.
State and data layers
- Server state (anything backed by Postgres/Supabase) flows through TanStack Query, keyed by resource and, where relevant, by user id, with explicit
staleTime/enabledguards so queries don't fire before auth is resolved. - Client-only/UI state uses local component state and React context (
src/contexts) for cross-cutting concerns such as pricing region. - Supabase client access is split between a browser client (
src/integrations/supabase/client.ts, anon key, RLS-enforced) and a server-only client (client.server.ts) used inside server functions/edge functions where elevated or session-bound access is required. - Form state uses
react-hook-formwithzodresolvers; the samezodschemas are frequently shared between client-side validation and server functioninputValidators.
Styling
The UI is built with Tailwind CSS (v4, via the @tailwindcss/vite plugin) and a component layer based on shadcn/ui primitives wrapping Radix UI (@radix-ui/react-*). Global styles live in src/styles.css; reusable primitives sit in src/components/ui, with feature-specific composition in src/components/* and page-level layout in src/pages.
Build and deploy
npm run devruns the app undervite devwith the TanStack Start plugin providing SSR in development.npm run buildfirst regenerates the sitemap (scripts/generate-sitemap.mjs) and then runsvite build, producing the SSR server bundle and client assets.- Edge functions under
supabase/functionsare deployed independently to the Supabase project as Deno functions and are not part of the Vite build; each has its ownindex.tsentry point. - Database schema changes are applied via timestamped SQL files in
supabase/migrations, run through the Supabase migration tooling — the application code never issues raw DDL at runtime. - Environment-specific values (Supabase project reference, publishable keys, third-party client IDs) are read from environment variables at build or runtime; secrets used by edge functions are configured as Supabase function secrets, not embedded in the client bundle.