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: setGOOGLE_REDIRECT_URIto your deployed/api/gsc/callbackURL 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
Scanrow (see Reports & persistence). JSON is served fromGET /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.
- Open APIs & Services → Credentials, select your client (or create one).
- 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. - Set
GOOGLE_REDIRECT_URIto that same callback URL so Google's redirect matches what Monix exchanges for tokens. - 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 (signedstate).GET /api/gsc/callback— OAuth redirect handler (must matchGOOGLE_REDIRECT_URI). Exchanges the code, stores refresh tokens encrypted at rest, then redirects toGSC_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 givensite_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 paramszone_id, optionaldays(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.