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 +
+
+
+
+ ); +}