Sajt · Docs

Routes

Every App Router route by area with its auth status, how a published site is reached, and the Convex HTTP endpoints public sites call.

Every route in the Next.js 16 App Router app, with its auth status. Routing middleware is proxy.ts — Next.js 16 names the file proxy, not middleware. Auth is Clerk; the protected matcher covers /dashboard(.*), /onboarding(.*), and /preview(.*).

Status values: working · partial · static-only · dev-only.

Marketing / public (no auth)

URLPurposeAuthFileStatus
/Marketing landing; redirects signed-in users to /dashboardpublicapp/page.tsxworking
/createAccountless AI generator (prompt → preview)publicapp/create/page.tsxworking
/pricingPlan cardspublicapp/pricing/page.tsxworking
/whats-newUser-facing changelogpublicapp/whats-new/page.tsxworking
/privacy-policyBilingual privacy policypublicapp/privacy-policy/page.tsxworking
/termsBilingual termspublicapp/terms/page.tsxworking
/downloadNative app downloadspublicapp/download/page.tsxworking
/blogMarketing blog index (file/MDX, lib/blog)publicapp/blog/page.tsxworking
/blog/[slug]Blog postpublicapp/blog/[slug]/page.tsxworking
/blog/tag/[slug], /blog/category/[slug]Filtered blog listspublicapp/blog/tag|category/[slug]/page.tsxworking
/blog/feed.xmlRSS/Atom feedpublicapp/blog/feed.xml/route.tsworking
/landing-lab, /landing-lab/*Design-system concept demospublicapp/landing-lab/**static-only

The marketing /blog is a separate, file-based blog. In-site news articles are pages rows (pageType: "post") that render under a published site's /news — not here. See Data model.

Auth

URLPurposeAuthFileStatus
/sign-in/[[...sign-in]]Clerk sign-inpublicapp/sign-in/[[...sign-in]]/page.tsxworking
/sign-up/[[...sign-up]]Clerk sign-uppublicapp/sign-up/[[...sign-up]]/page.tsxworking
/sso-callbackOAuth / social callbackpublicapp/sso-callback/page.tsxworking
/desktop/signinDesktop (Tauri) → system-browser OAuth launcherpublicapp/desktop/signin/page.tsxworking
/desktop/handoffMints a single-use Clerk ticket → sitebuilder:// deep linkprotectedapp/desktop/handoff/route.tsworking

App / dashboard (Clerk-gated)

URLPurposeAuthFileStatus
/dashboardResolver → last company or onboardinggatedapp/dashboard/page.tsxworking
/dashboard/billingSubscription + add-ons (Stripe checkout target)gatedapp/dashboard/billing/page.tsxworking (live Stripe gated)
/dashboard/imagesWorkspace AI Image Studio (shared across sites)gatedapp/dashboard/images/page.tsxworking
/onboardingNew-company setup wizardgatedapp/onboarding/page.tsxworking
/dashboard/websites/[websiteId]Company home (overview, modules, insights, settings modal)gatedapp/dashboard/websites/[websiteId]/page.tsxworking
…/[websiteId]/editorFull visual editorgated…/editor/page.tsxworking
…/[websiteId]/utseendeAppearance / themegated…/utseende/page.tsxworking
…/[websiteId]/settingsLegacy → redirects to company-home modalgated…/settings/page.tsxworking (redirect)
…/[websiteId]/settings/domainCustom-domain connect + DNSgated…/settings/domain/page.tsxworking (live gated)
…/[websiteId]/paymentsPayments / Stripe Connect dashboardgated…/payments/page.tsxworking
…/[websiteId]/payments/offersOffers / quotes managementgated…/payments/offers/page.tsxworking
…/[websiteId]/servicesServices / booking config (Phase S)gated…/services/page.tsxworking
…/[websiteId]/inboxLeads + bookings inboxgated…/inbox/page.tsxworking
…/[websiteId]/invoicesInvoice managementgated…/invoices/page.tsxworking

app/dashboard/layout.tsx adds the Convex Authenticated / Unauthenticated gate, the account-settings modal, and the image-job watcher. The path segment under /dashboard/websites/<…> is the company's public slug (id or slug both resolve via websites.resolveRoute, which canonical-redirects bare ids to the slug form).

Preview

URLPurposeAuthFileStatus
/preview/[websiteId]/[[...path]]Owner's live draft previewgatedapp/preview/[websiteId]/[[...path]]/page.tsxworking (reads draft tables)
/p/[token]/[[...path]]Shareable always-live preview (public or password)token-gatedapp/p/[token]/[[...path]]/page.tsxworking

Both preview surfaces read draft tables through the same SectionRenderer as production — they never read snapshots. See Publishing.

Published public site

URLPurposeAuthFileStatus
/site/[slug]/[[...path]]Published site (snapshot only)publicapp/site/[slug]/[[...path]]/page.tsxworking (SSR from getPublishedSnapshot)
/site/[slug]/<locale>/...Secondary-language pages (/sv, /en)publicsame fileworking
/site/[slug]/sitemap.xmlPer-site sitemap (hreflang, respects noindex)publicapp/site/[slug]/sitemap.xml/route.tsworking
/site/[slug]/ogOn-demand OG share image (next/og, rendered from the snapshot)publicapp/site/[slug]/og/route.tsxworking

How a published site is reached (proxy.ts)

The middleware resolves an incoming request host to a site slug in three ways:

  1. Subdomain<slug>.<NEXT_PUBLIC_SITES_DOMAIN> rewrites to /site/<slug>. The slug is the subdomain label, so no Convex lookup is needed. Dormant until NEXT_PUBLIC_SITES_DOMAIN is set (sitesSubdomainLabel).
  2. Custom domain — any host that is not the app host and not inside the platform's own sites zone is resolved through the Convex /host-slug?host=… endpoint (answer cached ~60s), then rewritten to /site/<slug>. The rewrite sets x-sajt-host: custom (so the site emits root-relative links) and x-sajt-lang. Only domains with status === "active" / LIVE resolve (convex/domains.ts getSlugByHost).
  3. Path — a direct /site/<slug>/... request, language-tagged via the /site-lang endpoint for the root layout's <html lang>.

Canonical precedence is custom domain > subdomain > path. The <html lang> is the explicit /en | /sv segment when present, else the looked-up primary language, else sv.

Customer-facing token pages (no account)

URLPurposeAuthFileStatus
/invite/[token]Accept a team invite (sign-in if email differs)token / publicapp/invite/[token]/page.tsxworking
/invoice/[token]Customer invoice view + pay linktoken-gatedapp/invoice/[token]/page.tsxworking
/offert/[token]Customer offer (accept / decline)token-gatedapp/offert/[token]/page.tsxworking

API routes (Next.js route handlers)

URLMethodPurposeFile
/api/stripe/billing-webhookPOSTSubscription events → api.billing.handleStripeEventapp/api/stripe/billing-webhook/route.ts
/api/stripe/connect-webhookPOSTConnect payments → api.stripeConnect.handleConnectEventapp/api/stripe/connect-webhook/route.ts
/api/stripe/domain-webhookPOSTDomain-purchase checkout → api.domainsBuy.completeDomainPurchaseFromWebhookapp/api/stripe/domain-webhook/route.ts
/api/export/[websiteId]/inboxGETCSV (leads + bookings) zip export (owner-gated)app/api/export/[websiteId]/inbox/route.ts
/api/export/[websiteId]/staticGETStatic HTML site export (owner-gated)app/api/export/[websiteId]/static/route.ts
/.well-known/apple-app-site-associationGETiOS Universal Links configapp/.well-known/apple-app-site-association/route.ts

Stripe webhooks accept the raw body + signature and forward to a Convex action that verifies the signature server-side. Export routes call Clerk auth() + Convex requireWebsiteAccess in the handler.

Convex HTTP endpoints (convex/http.ts)

A separate Convex HTTP router serves the public, no-auth, CORS-open endpoints that published sites and the proxy call. The site is resolved by slug server-side, and writes go only into runtime tables.

PathMethodPurpose
/trackPOSTDay-bucketed analytics beacon → internal.analytics.record
/leadPOSTLead-form capture → internal.analytics.recordLead
/booking-slotsPOSTRead taken slots for a date → internal.bookings.takenSlots
/bookingPOSTCreate a native booking → internal.bookings.recordBooking
/host-slugGETHost → site slug (custom-domain routing) → api.domains.getSlugByHost
/site-langGETSlug → primary language → api.publicSiteMeta.getPrimaryLang

The write endpoints (/lead, /booking) are rate-limited transactionally (keyed by slug / IP) and re-validate against the published config — the client is never trusted. See Auth & permissions.

Synthesis note: the OG share image is a Next.js route handler (app/site/[slug]/og/route.tsx), not a Convex HTTP endpoint. The two routers are distinct: /api/* and /og are Next.js handlers; the table above is the Convex router.

Top-level / sitewide

URLPurposeFile
/robots.txtDisallows /dashboard/, /onboarding, /preview/, /api/app/robots.ts
/sitemap.xmlApp-level sitemapapp/sitemap.ts

Native-shell routing notes

  • Desktop (Sajt-Desktop UA): the homepage bounces to /dashboard; protected routes are gated; OAuth runs via the system browser → /desktop/handoff.
  • iOS (Sajt-iOS UA): marketing bounces to /sign-in?redirect_url=/dashboard; protected routes are gated; OAuth via the system browser + sitebuilder:// deep link.

Dev-only routes

/deveditor and /devpreview are mock-data harnesses that call notFound() when NODE_ENV === "production". /landing-lab calls notFound() when VERCEL_ENV === "production" (so it is visible on preview deployments but never on the production domain). See Deployment.

On this page