Monix

How everything fits together

This guide matches the repo: Next.js route handlers run the scan pipeline, persist results in Supabase Postgres, verify Supabase JWTs for authenticated APIs, and orchestrate Google Search Console OAuth and Cloudflare using server-side secrets. The same Next.js app is where you sign in and manage sites, scans, and integrations.

Overview

Monix analyzes a public URL and produces category scores (security, SEO, performance) plus an overall score. Each run is stored as a Scan row (JSON payload, score, expiry) so you can reopen reports and list history. The product surface is the authenticated dashboard: you sign in with Supabase, add monitored sites (targets), run scans, and browse results. Optionally connect Google Search Console for search analytics and Cloudflare for edge HTTP metrics when hostnames match your zones.

Using the product

  • Sign in — The browser authenticates with Supabase Auth. The Next.js app sends Authorization: Bearer <JWT> to /api/*; the server verifies the JWT (JWKS or HS256 in tests) and syncs profile rows in Postgres. App sign-in is Supabase Auth (email/password or Google via Supabase). Search Console OAuth is a separate Google consent flow: set GOOGLE_REDIRECT_URI to your deployed /api/gsc/callback URL so Google returns the authorization code to Monix.
  • Overview — The dashboard home summarizes activity, average scores, Search Console rollups when connected, and Cloudflare edge totals when a monitored hostname matches a zone on your API token.
  • Analytics — Search Console tables (per-target summaries, top queries, sync) plus Cloudflare edge charts when connected.
  • Sites — Monitored URLs (targets). Add a site, run scans, and link Search Console properties when the URL matches a verified property. Cloudflare metrics appear automatically when the hostname matches a zone on your token—no per-site toggle.
  • Integrations Dashboard → Integrations for Google Search Console and Cloudflare connection status.
  • Scan history — Lists completed scans; open a row to view the full report for that run.
  • Reports — Each scan is persisted as a Scan row (see Reports & persistence). JSON is served from GET /api/reports/<uuid>/ (shareable until expiry). The app also surfaces reports under /dashboard/report/<id>.
  • Profile & settings — Account and preferences from the sidebar.

Google OAuth (Console setup)

If Google shows does not comply with Google's OAuth 2.0 policy or asks you to register the redirect URI, fix it in Google Cloud Console — not in Monix code. Use one OAuth 2.0 Client ID with type Web application.

  1. Open APIs & Services → Credentials, select your client (or create one).
  2. Under Authorized redirect URIs, click Add URI and paste exactly (local Next.js; path must match GOOGLE_REDIRECT_URI):http://localhost:3000/api/gsc/callbackProduction: use your real origin, e.g. https://app.example.com/api/gsc/callback.
  3. Set GOOGLE_REDIRECT_URI to that same callback URL so Google's redirect matches what Monix exchanges for tokens.
  4. Under OAuth consent screen: if publishing status is Testing, add your Google account under Test users. Save and wait a minute before retrying.

Google Search Console

Monix can read Search Analytics (read-only) and list your verified sites using Google's OAuth 2.0 flow. Refresh tokens are stored encrypted in PostgreSQL; the browser never holds long-lived secrets. This is separate from signing in with Google for Monix itself—GSC uses its own consent screen and scopes (webmasters.readonly, plus standard OpenID profile scopes).

What you see in the app

  • Overview — Totals and charts built from cached per-target metrics when at least one target has synced data.
  • Analytics — Full table of targets with property URLs, sync errors, summary numbers, and top queries.
  • Matching — For each target URL, the server picks a Search Console property you have verified (URL-prefix or domain) that matches the target host. If nothing matches, the target records a clear sync message instead of failing the rest of the app.

When data syncs

  • After you connect GSC, creating a new target triggers a sync attempt for that target (server-side).
  • Use Sync Search Console on Analytics (or the equivalent API) to re-fetch metrics for every target—useful right after connecting or when you add properties in Google.

Search Console API routes

  • GET /api/gsc/connect/ — Returns JSON with a Google authorization URL (signed state).
  • GET /api/gsc/callback — OAuth redirect handler (must match GOOGLE_REDIRECT_URI). Exchanges the code, stores refresh tokens encrypted at rest, then redirects to GSC_OAUTH_SUCCESS_URL / error URL.
  • GET /api/gsc/status/ — Whether the current user has stored credentials.
  • GET /api/gsc/sites/ — List verified sites (for debugging or future UI).
  • POST /api/gsc/analytics/ — On-demand analytics for a given site_url (JSON body; optional date range).
  • POST /api/gsc/sync-targets/ — Re-sync all targets for the user.
  • POST /api/gsc/disconnect/ — Remove stored Search Console tokens for the current user.

Register the redirect URI in Google Cloud exactly as GOOGLE_REDIRECT_URI. The default in .env.example is http://localhost:3000/api/gsc/callback. Success and error browser redirects after OAuth use GSC_OAUTH_SUCCESS_URL and GSC_OAUTH_ERROR_URL (defaults point at the Next.js app with query flags).

Cloudflare

Users paste a Cloudflare API token in the app. The Next.js server verifies it, encrypts it with the same Fernet machinery as GSC tokens, and calls Cloudflare API v4 (REST for zones and token verify; GraphQL for zone HTTP request analytics). No Cloudflare secrets live in the browser.

Create a custom token with Zone → Zone → Read and Zone → Analytics → Read for the zones you need. The dashboard matches each monitored site hostname to a zone on that token and rolls up requests, cached bytes, threats, and country breakdowns on Overview, Sites, Analytics, and Issues.

Cloudflare API routes

  • GET /api/cloudflare/status/ — Connection status and account summary.
  • POST /api/cloudflare/connect/ — JSON body { "api_token": "…" }; verifies and stores the token.
  • DELETE /api/cloudflare/disconnect/ — Remove stored credentials.
  • GET /api/cloudflare/zones/ — List zones visible to the token.
  • GET /api/cloudflare/analytics/ — Query params zone_id, optional days (default 7).

Architecture

Browser (Next.js UI)
       │
       └─► Next.js Route Handlers (/api/*) — Supabase JWT auth, scan pipeline,
           targets/scans persistence, GSC OAuth, Cloudflare API proxy

Supabase Postgres — monix_* tables (users, targets, scans, credentials).

Google APIs — Search Console via stored OAuth refresh tokens.

Cloudflare — api.cloudflare.com (zones, GraphQL HTTP analytics) via stored API token.

When you trigger a scan from the authenticated app, the server validates the bearer token, associates the run with a target when provided, and runs the TypeScript scan pipeline. Results are written to monix_scans (see Reports & persistence below).

Reports & persistence

Scan outcomes are stored in public.monix_scans: a unique report_id (UUID), the scanned URL, composite score, full results JSON from the engine, optional target_id reference, and expires_at / is_expired for shareable report lifetime (default TTL 30 days when persisted).

GET /api/reports/<uuid>/ returns the JSON payload for non-expired scans (used for public sharing and the in-app report viewer). List endpoints under /api/scans/ return metadata for the signed-in user's targets.

Scan engine

The scan pipeline lives in web/src/server/analysis/ (TypeScript). It covers TLS, DNS, headers, redirects, cookies, geo, optional port checks, and—when configured—PageSpeed, then feeds category scores. The response model includes findings, recommendations, summaries, and raw fields the UI renders in tables and panels.

Next.js API & data

Authenticated JSON lives under /api/ as Next.js route handlers. The Supabase service role key is used only on the server to read and write monix_* tables. Google Search Console and Cloudflare integration endpoints are /api/gsc/* and /api/cloudflare/*.

Next.js web app

The web/ app uses the App Router. API calls are centralized in src/lib/api.ts: the browser calls same-origin /api/*. For server-side rendering, set NEXT_PUBLIC_SITE_URL to the public app origin so fetches resolve correctly outside the browser.

What gets analyzed

Security

  • TLS / certificate validity
  • Security headers and cookie flags
  • DNS and host intelligence
  • Ports, redirects, tech fingerprint
  • Geo / IP context

SEO

  • Title, meta description, Open Graph
  • robots.txt and sitemap signals
  • Canonical and heading structure
  • Optional: GSC clicks, impressions, queries (OAuth)
  • Optional: Cloudflare edge requests & threats (API token)

Performance

  • Google PageSpeed when an API key is set
  • Core Web Vitals and lab metrics
  • Accessibility and best-practices scores

Configuration

See repository .env.example for the full list. Commonly:

NEXT_PUBLIC_SUPABASE_URL / NEXT_PUBLIC_SUPABASE_ANON_KEY
Supabase project URL and anon key for browser auth.
SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY
Server-only: Postgres access and admin operations from route handlers.
SUPABASE_JWKS_URL / SUPABASE_JWT_AUD
JWT verification for production (RS256 via JWKS).
NEXT_PUBLIC_SITE_URL
Public site origin for SSR fetches to /api/* (no trailing slash).
PAGESPEED_API_KEY
Optional; improves PageSpeed Insights rate limits.
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET
OAuth client for Search Console API access; create in Google Cloud Console.
GOOGLE_REDIRECT_URI
Must match Authorized redirect URIs — typically https://your-app/api/gsc/callback (local: http://localhost:3000/api/gsc/callback).
GSC_OAUTH_SUCCESS_URL / GSC_OAUTH_ERROR_URL
Where the browser lands after GSC OAuth (defaults: Next.js Projects page with query flags).
GOOGLE_REFRESH_TOKEN_FERNET_KEY
Optional Fernet key for stored GSC refresh tokens and Cloudflare API tokens; if unset, a key is derived from MONIX_FERNET_SECRET or SUPABASE_SERVICE_ROLE_KEY.

Local development

Copy .env.example to .env at the repo root and configure Supabase plus Google OAuth as documented above. Apply SQL in supabase/migrations/ to your Supabase Postgres project. Then run the Next.js app in web/ with bun install and bun run dev. Use bun run test and bun run build before shipping. Optionally run ./setup.sh web from the repo root.