From add176c3cce87d966600bc34e98b1ef557d48cb4 Mon Sep 17 00:00:00 2001 From: Ben Senescu <44480372+bensenescu@users.noreply.github.com> Date: Thu, 2 Apr 2026 00:08:30 -0400 Subject: [PATCH] Add pricing page (#58) * refactor: move lighthouse audits to dataforseo (#43) * refactor: move lighthouse audits to dataforseo * chore: remove obsolete audit settings modal * refactor: rename psi flows to lighthouse * save * refactor: simplify audit lighthouse storage flow * fix: separate lighthouse metrics from actionable audits * refactor: remove redundant audit project inputs * feat: redesign lighthouse issues screen with score gauges and table layout Replace flat score cards with circular SVG gauges, condense metrics into a compact grid, and switch issue list from cards to an expandable table with fixed column widths. Co-Authored-By: Claude Opus 4.6 (1M context) * test: harden lighthouse regression coverage * fix: restore project-scoped audit inputs * refactor: simplify lighthouse payload handling * refactor: inline lighthouse server handlers * refactor: share audit workflow types * refactor: simplify lighthouse payload flows * save * refactor: drop project pagespeed api key * fix: restore lighthouse issues loading with resilient project context * fix: restore audit issues back navigation * refactor: simplify project context and lighthouse error handling * fix: tolerate DataForSEO lighthouse payload drift * refactor: route audit lighthouse through dataforseo client --------- Co-authored-by: Claude Opus 4.6 (1M context) * refactor: move DataForSEO response cache from KV to R2 (#46) * refactor: move lighthouse audits to dataforseo (#43) * refactor: move lighthouse audits to dataforseo * chore: remove obsolete audit settings modal * refactor: rename psi flows to lighthouse * save * refactor: simplify audit lighthouse storage flow * fix: separate lighthouse metrics from actionable audits * refactor: remove redundant audit project inputs * feat: redesign lighthouse issues screen with score gauges and table layout Replace flat score cards with circular SVG gauges, condense metrics into a compact grid, and switch issue list from cards to an expandable table with fixed column widths. Co-Authored-By: Claude Opus 4.6 (1M context) * test: harden lighthouse regression coverage * fix: restore project-scoped audit inputs * refactor: simplify lighthouse payload handling * refactor: inline lighthouse server handlers * refactor: share audit workflow types * refactor: simplify lighthouse payload flows * save * refactor: drop project pagespeed api key * fix: restore lighthouse issues loading with resilient project context * fix: restore audit issues back navigation * refactor: simplify project context and lighthouse error handling * fix: tolerate DataForSEO lighthouse payload drift * refactor: route audit lighthouse through dataforseo client --------- Co-authored-by: Claude Opus 4.6 (1M context) * refactor: move DataForSEO response cache from KV to R2 KV TTL-based expiry is imprecise for cache freshness. Switch to R2 with soft TTL via custom metadata (expiresAt) and a 7-day lifecycle rule for cleanup. Cache objects live under the `dataforseo-cache/` prefix, separate from durable audit payloads in `site-audit/`. Co-Authored-By: Claude Opus 4.6 (1M context) * refactor: use Workers crypto for R2 cache keys Keep the cache helper aligned with the Cloudflare runtime and avoid pulling Worker bindings into Vitest. Also clarify that R2 lifecycle cleanup is optional but recommended to control storage growth. --------- Co-authored-by: Claude Opus 4.6 (1M context) * refactor: move hosted billing to Autumn hooks (#48) * refactor: move lighthouse audits to dataforseo (#43) * refactor: move lighthouse audits to dataforseo * chore: remove obsolete audit settings modal * refactor: rename psi flows to lighthouse * save * refactor: simplify audit lighthouse storage flow * fix: separate lighthouse metrics from actionable audits * refactor: remove redundant audit project inputs * feat: redesign lighthouse issues screen with score gauges and table layout Replace flat score cards with circular SVG gauges, condense metrics into a compact grid, and switch issue list from cards to an expandable table with fixed column widths. Co-Authored-By: Claude Opus 4.6 (1M context) * test: harden lighthouse regression coverage * fix: restore project-scoped audit inputs * refactor: simplify lighthouse payload handling * refactor: inline lighthouse server handlers * refactor: share audit workflow types * refactor: simplify lighthouse payload flows * save * refactor: drop project pagespeed api key * fix: restore lighthouse issues loading with resilient project context * fix: restore audit issues back navigation * refactor: simplify project context and lighthouse error handling * fix: tolerate DataForSEO lighthouse payload drift * refactor: route audit lighthouse through dataforseo client --------- Co-authored-by: Claude Opus 4.6 (1M context) * refactor: move hosted billing to Autumn hooks * fix: satisfy ci checks for hosted billing * refactor: simplify hosted Autumn billing integration Limit Autumn wiring to the billing route and remove billing-specific indirection so the hosted billing flow is easier to reason about. Stop sending organization IDs as fake customer names to keep customer identity data honest. * fix: satisfy ci line-limit check for hosted billing --------- Co-authored-by: Claude Opus 4.6 (1M context) * Add pricing page * merge * Revert home page back to normal --------- Co-authored-by: Claude Opus 4.6 (1M context) --- web/src/routeTree.gen.ts | 21 ++++ web/src/routes/pricing.tsx | 206 +++++++++++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 web/src/routes/pricing.tsx diff --git a/web/src/routeTree.gen.ts b/web/src/routeTree.gen.ts index 0c570c2..00c6f52 100644 --- a/web/src/routeTree.gen.ts +++ b/web/src/routeTree.gen.ts @@ -10,6 +10,7 @@ import { Route as rootRouteImport } from './routes/__root' import { Route as PrivacyRouteImport } from './routes/privacy' +import { Route as PricingRouteImport } from './routes/pricing' import { Route as IndexRouteImport } from './routes/index' import { Route as BlogsIndexRouteImport } from './routes/blogs/index' import { Route as JsScriptDotjsRouteImport } from './routes/js/script[.]js' @@ -22,6 +23,11 @@ const PrivacyRoute = PrivacyRouteImport.update({ path: '/privacy', getParentRoute: () => rootRouteImport, } as any) +const PricingRoute = PricingRouteImport.update({ + id: '/pricing', + path: '/pricing', + getParentRoute: () => rootRouteImport, +} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', @@ -55,6 +61,7 @@ const ApiEventRoute = ApiEventRouteImport.update({ export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/pricing': typeof PricingRoute '/privacy': typeof PrivacyRoute '/api/event': typeof ApiEventRoute '/api/subscribe': typeof ApiSubscribeRoute @@ -64,6 +71,7 @@ export interface FileRoutesByFullPath { } export interface FileRoutesByTo { '/': typeof IndexRoute + '/pricing': typeof PricingRoute '/privacy': typeof PrivacyRoute '/api/event': typeof ApiEventRoute '/api/subscribe': typeof ApiSubscribeRoute @@ -74,6 +82,7 @@ export interface FileRoutesByTo { export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute + '/pricing': typeof PricingRoute '/privacy': typeof PrivacyRoute '/api/event': typeof ApiEventRoute '/api/subscribe': typeof ApiSubscribeRoute @@ -85,6 +94,7 @@ export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' + | '/pricing' | '/privacy' | '/api/event' | '/api/subscribe' @@ -94,6 +104,7 @@ export interface FileRouteTypes { fileRoutesByTo: FileRoutesByTo to: | '/' + | '/pricing' | '/privacy' | '/api/event' | '/api/subscribe' @@ -103,6 +114,7 @@ export interface FileRouteTypes { id: | '__root__' | '/' + | '/pricing' | '/privacy' | '/api/event' | '/api/subscribe' @@ -113,6 +125,7 @@ export interface FileRouteTypes { } export interface RootRouteChildren { IndexRoute: typeof IndexRoute + PricingRoute: typeof PricingRoute PrivacyRoute: typeof PrivacyRoute ApiEventRoute: typeof ApiEventRoute ApiSubscribeRoute: typeof ApiSubscribeRoute @@ -130,6 +143,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof PrivacyRouteImport parentRoute: typeof rootRouteImport } + '/pricing': { + id: '/pricing' + path: '/pricing' + fullPath: '/pricing' + preLoaderRoute: typeof PricingRouteImport + parentRoute: typeof rootRouteImport + } '/': { id: '/' path: '/' @@ -177,6 +197,7 @@ declare module '@tanstack/react-router' { const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, + PricingRoute: PricingRoute, PrivacyRoute: PrivacyRoute, ApiEventRoute: ApiEventRoute, ApiSubscribeRoute: ApiSubscribeRoute, diff --git a/web/src/routes/pricing.tsx b/web/src/routes/pricing.tsx new file mode 100644 index 0000000..56436f8 --- /dev/null +++ b/web/src/routes/pricing.tsx @@ -0,0 +1,206 @@ +import { createFileRoute, Link } from "@tanstack/react-router"; +import { buildPageSeo } from "@/lib/seo"; + +export const Route = createFileRoute("/pricing")({ + head: () => + buildPageSeo({ + title: "Pricing", + description: + "OpenSEO is free to self-host. The managed service is $10/month and includes Usage Credits.", + path: "/pricing", + titleSuffix: "OpenSEO", + }), + component: Pricing, +}); + +function NavLink({ to, children }: { to: string; children: React.ReactNode }) { + return ( + + {children} + + ); +} + +function Pricing() { + return ( +
+
+ {/* Nav */} + + + {/* Headline */} +

Pricing

+ + {/* Managed */} +
+

Managed

+
+ {/* Base Plan */} +
+
+

Base Plan

+

$10/month

+
+
    + {[ + "Access to OpenSEO's managed service", + "Includes $10.00 of Usage Credits each billing cycle", + "Monthly included credits reset each cycle", + ].map((item) => ( +
  • + + — + + {item} +
  • + ))} +
+
+ + {/* Usage Credits — add-on, visually connected to Base Plan */} +
+
+

Usage Credits

+ + add-on + +
+

+ Credits are consumed as you use the app. We use powerful + providers for AI and SEO data. We bill based on usage of those + APIs and charge a small premium. +

+
    + {[ + "Purchase top-up credits if you use up your monthly credits", + "Top-up credits roll over and don't expire", + ].map((item) => ( +
  • + + — + + {item} +
  • + ))} +
+
+
+
+ + {/* Self-hosted */} +
+
+

Self-hosted

+
+

+ Deploy OpenSEO yourself via Docker or Cloudflare Workers. Bring your + own API keys and pay DataForSEO and other API providers directly. +

+ + View on GitHub + + +
+ + {/* FAQ */} +
+

FAQ

+
+
+
+ What features use credits vs. what's unlimited? +
+
+ Credits are consumed by features that query DataForSEO's API — + backlinks, keyword volume, competitor data, and site audits. + Your projects, settings, and any data already fetched don't cost + credits. +
+
+
+
+ Do unused credits roll over? +
+
+ Top-up credits roll over indefinitely. The Usage Credits + included with your Base Plan reset each billing cycle. +
+
+
+
Can I cancel anytime?
+
+ Yes. Cancel from your billing portal at any time. Your access + continues through the end of the current billing period.{" "} +
+
+
+
+ Do I need a subscription or just usage credits? +
+
+ While top-up Usage Credits roll over and don't expire, you need + an active subscription in order to use OpenSEO. +
+
+
+
+ + {/* Footer */} +
+
+ + GitHub + + + Discord + + Pricing + Privacy +
+
+
+
+ ); +}