Performance Characteristics of Open-SEO: Edge-Optimized Architecture Explained

Open-SEO enforces strict Google Search Console API limits, implements multi-tier caching with Cloudflare R2 and KV, and maintains lean database schemas to deliver sub-50ms response times at the edge.

Open-SEO is a Cloudflare-Workers-based SaaS that aggregates search analytics from Google Search Console (GSC) and third-party APIs. According to the every-app/open-seo source code, the platform handles performance through explicit row caps, aggressive edge caching, and graceful degradation patterns designed specifically for serverless runtime constraints.

Google Search Console API Limits and Pagination

The platform handles GSC's rigid API constraints through careful request construction and client-side pagination logic.

Enforced Row Caps

The codebase defines strict constants to prevent exceeding GSC's service quotas. In src/server/features/gsc/searchAnalytics.ts, the system enforces GSC_DEFAULT_ROW_LIMIT = 1000 and GSC_MAX_ROW_LIMIT = 1000. These caps ensure that single API calls never request more data than Google allows, preventing rate-limit errors and excessive latency.

Optimized Pagination Detection

For table views in the UI, the server function in src/serverFunctions/searchPerformance.ts implements an efficient pagination strategy. Rather than executing a separate count query to determine if more pages exist, the function fetches pageSize + 1 rows. If the extra row exists, the system sets hasNextPage: true and trims the result before returning the payload to the client. This eliminates an expensive additional database round-trip.

Export Row Limits

CSV and Google Sheets exports respect the same API constraints. The EXPORT_ROW_LIMIT constant (set to 1000) caps exported datasets at the GSC per-call maximum. This prevents timeout errors during large data dumps while ensuring compatibility with the underlying API restrictions.

Multi-Tier Caching Strategy

Open-SEO minimizes external API calls through a layered caching architecture utilizing Cloudflare's edge storage services.

R2 Object Storage for Heavy Payloads

Expensive operations such as Lighthouse audits and AI search prompts are cached in Cloudflare R2 with soft TTLs stored in object metadata. The implementation in src/server/lib/r2-cache.ts writes payloads to R2 after the first fetch, making subsequent reads cheap "hot reads" from the edge. This pattern prevents redundant processing of resource-intensive calculations.

KV Store for Short-Lived Data

Per-country location lookups and other small blobs are stored in Cloudflare KV with a 30-day TTL. The src/server/lib/dataforseo/serp-locations.ts module checks KV before querying external location APIs, reducing repeat fetches for frequently requested geographic data.

Database and Storage Optimization

The platform keeps relational database load minimal by design.

Lean Schema Design

According to the schema definitions in drizzle.config.ts, D1 (SQLite) and Postgres tables store primarily project metadata and user-generated content. Most read-only analytics are served directly from the R2 and KV cache layers, meaning the database mainly handles transactional writes rather than heavy analytical queries. This separation reduces connection pool pressure and query execution time.

Edge Runtime Constraints and Mitigation

Running on Cloudflare Workers imposes strict computational boundaries that shape the application's performance profile.

Worker CPU Limits

Cloudflare Workers enforce CPU-time limits of approximately 50 milliseconds per request. To respect these constraints, Open-SEO moves heavy lifting—such as large batch API calls and data processing—into background tasks or scheduled workers implemented in src/server/mcp/tools/* (e.g., search-console-tools.ts). This architecture prevents synchronous requests from exceeding runtime limits while still handling complex data pipelines asynchronously.

Graceful Degradation

When GSC authentication tokens are missing or revoked, the GscService.ts file detects the condition using the isExpectedGrantFailure method. Rather than throwing an error that bubbles up and breaks the request, the service returns a lightweight payload with connected: false. This pattern ensures the UI renders instantly even when third-party integrations are unavailable, maintaining perceived performance.

Implementation Examples

Fetching Paginated Performance Data

The following client call requests page 2 of query data with 25 rows per page:

// Client side – request page 2 of the “query” dimension
const result = await getSearchPerformanceTable({
  projectId,
  dateRange: "last_28_days",
  dimension: "query",
  page: 2,
  pageSize: 25,
});

The server handler in src/serverFunctions/searchPerformance.ts automatically adds rowLimit: pageSize + 1 and the appropriate startRow offset to the underlying GSC request, then trims the excess row before returning the result.

Exporting Capped Datasets

When exporting search performance data, the system respects the 1000-row limit:

const csv = await exportSearchPerformanceTable({
  projectId,
  dateRange: "last_7_days",
  dimension: "page",
});

This ensures the export operation completes within API constraints and edge function timeouts.

Utilizing R2 Cache for Expensive Operations

The following pattern demonstrates caching Lighthouse reports in R2:

import { getJsonFromR2, putTextToR2 } from "@/server/lib/r2";

// Attempt cheap read first
let payload = await getJsonFromR2("lighthouse/12345");

// On cache miss, fetch from origin and cache with soft TTL
if (!payload) {
  payload = await fetchLighthouseFromOrigin(...);
  await putTextToR2("lighthouse/12345", payload);
}

All R2 interactions route through src/server/lib/r2.ts and the higher-level cache logic in src/server/lib/r2-cache.ts.

Summary

  • Hard API limits: The system enforces 1000-row maximums for GSC queries and exports to prevent rate-limiting errors.
  • Efficient pagination: Fetching pageSize + 1 rows eliminates extra count queries while detecting subsequent pages.
  • Multi-layer caching: R2 stores heavy payloads with soft TTLs, while KV caches small lookup data for 30 days.
  • Lean database: D1/Postgres schemas minimize analytical queries by serving cached data from edge storage.
  • Edge constraints: Background workers handle CPU-intensive tasks, while graceful degradation ensures fast failure for auth errors.

Frequently Asked Questions

How does Open-SEO handle Google Search Console API rate limits?

The system enforces GSC_MAX_ROW_LIMIT = 1000 in src/server/features/gsc/searchAnalytics.ts, ensuring no single request exceeds Google's row limit. For large datasets, the searchAnalytics function implements pagination using the startRow parameter to fetch data in bounded chunks.

What is the maximum number of rows Open-SEO can export?

Exports are capped at 1,000 rows via the EXPORT_ROW_LIMIT constant defined in src/serverFunctions/searchPerformance.ts. This aligns with the GSC API maximum and prevents timeout errors during CSV generation.

How does Open-SEO cache expensive API operations?

The platform uses Cloudflare R2 for heavy payloads like Lighthouse reports and AI search results, implemented in src/server/lib/r2-cache.ts. This cache uses a soft TTL stored in object metadata. Smaller data such as country locations are cached in Cloudflare KV with a 30-day TTL.

What happens when GSC authentication fails?

The GscService.ts module detects token errors using the isExpectedGrantFailure method and returns a lightweight { connected: false } payload instead of throwing an exception. This ensures the UI loads instantly while gracefully handling missing credentials.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →