How Rank Tracking Works in OpenSEO with Scheduled SERP Checks
OpenSEO tracks keyword positions by creating a rank-tracking config that runs on manual, daily, weekly, or monthly schedules, using a Cloudflare cron job to trigger SERP checks and advance the next run time to prevent schedule drift.
Rank tracking in OpenSEO combines configurable scheduling with automated SERP monitoring to track keyword positions across devices and locations. The open-source codebase in every-app/open-seo implements a drift-free scheduling system that handles credit validation, workflow orchestration, and result persistence. This article examines the technical implementation of scheduled SERP checks, from config definition to final snapshot storage.
Understanding the Rank Tracking Configuration
Each rank-tracking session begins with a configuration that binds a domain to specific search parameters. In src/shared/rank-tracking.ts, the system stores the scheduleInterval as a string enum with values "daily", "weekly", "monthly", or "manual".
The helper function isScheduledRankTrackingInterval determines whether a config qualifies for automatic execution. This check runs before any scheduling logic to filter out manually triggered configurations.
Computing the Next Check Time
The scheduling engine relies on computeNextCheckAt in src/shared/rank-tracking.ts (lines 21-66) to generate UTC timestamps for future runs. This function handles three distinct interval types while preventing schedule drift from delayed executions.
Handling Monthly, Daily, and Weekly Intervals
For monthly configurations, the algorithm calculates the end of the next month and selects a random hour between 04:00 and 09:00 UTC to distribute load. Daily configs add exactly 24 hours to the previous anchor time, while weekly configs advance by 7 days. Both daily and weekly intervals also inject random hours and minutes to prevent thundering herd problems.
Preventing Schedule Drift
If a previous nextCheckAt timestamp is provided, the function advances from that anchor point until the result lies in the future. This ensures that delayed runs—caused by downtime or credit shortages—do not create a permanent offset in the schedule. The system catches up to real-time before calculating the next valid slot.
The Scheduled Check Cron Job
Cloudflare Workers execute runScheduledRankChecks every minute from src/server/features/rank-tracking/services/scheduledRankChecks.ts (lines 10-93). This cron handler serves as the entry point for all automated rank tracking.
Finding Due Configurations
The worker queries RankTrackingRepository.getDueConfigsWithOrganization to fetch configurations where nextCheckAt is less than or equal to the current timestamp. For each due config, the system performs two validation checks:
- Skips the config if the organization lacks a paid plan in hosted mode
- Skips and advances the schedule if the config contains no keywords
Workflow Initialization
For valid configs, the cron immediately updates nextCheckAt using computeNextCheckAt before starting the workflow. This early advancement prevents retry storms if the subsequent workflow fails. The system then invokes beginRankCheckRun to initiate the RankCheckWorkflow.ts orchestration.
Executing the Rank Check Workflow
The workflow defined in src/server/workflows/RankCheckWorkflow.ts (lines 25-86) manages the actual SERP retrieval and data persistence.
Credit Estimation and Validation
Before executing checks, the workflow calls estimateRankCheckCredits from src/shared/rank-tracking.ts (lines 65-76). This function multiplies keyword count by device count and SERP depth cost, applies a markup, and converts USD to internal credits. Scheduled checks always use the cheaper queued pricing model rather than live check rates.
The workflow verifies the organization's credit balance in hosted mode, halting execution if insufficient funds exist.
SERP Execution and Result Persistence
The system executes SERP requests via runLiveCheck or runQueuedCheck depending on configuration. After each batch completes, the workflow writes position snapshots to the database. Upon completion, finalizeRankCheckRun marks the run as completed, records total keywords checked, and updates the config's lastCheckedAt timestamp. Notably, the schedule advancement occurred earlier in the cron handler, ensuring the next check time remains independent of workflow duration.
Code Examples
The following examples demonstrate creating configurations and triggering checks using the server functions defined in src/serverFunctions/rank-tracking.ts:
// 1️⃣ Create a rank-tracking config (client side)
await createRankTrackingConfig({
domain: "example.com",
locationCode: 2840, // US / New York
languageCode: "en",
devices: "both",
serpDepth: 30, // 3 pages (10 results each)
scheduleInterval: "weekly", // auto-run weekly
});
// 2️⃣ Manually trigger a check (e.g. after adding keywords)
await triggerRankCheck({
configId: "<config-id>",
keywordIds: ["kw1", "kw2"], // optional – limit to specific keywords
});
// 3️⃣ How the cron advances the schedule (internal)
await runScheduledRankChecks(env); // called by the Cloudflare worker every minute
Summary
- Rank tracking configs store schedule intervals in
src/shared/rank-tracking.tswith options for daily, weekly, monthly, or manual execution. - Drift-free scheduling uses
computeNextCheckAtto anchor future runs from the previous check time, with random UTC hours between 04:00-09:00 for monthly checks. - Cloudflare cron workers execute
runScheduledRankChecksevery minute to find due configs, validate credits, and advance schedules before starting workflows. - Credit estimation occurs in
estimateRankCheckCredits, applying queued pricing and USD-to-credit conversion rules fromsrc/shared/billing.ts. - Workflow finalization in
RankCheckWorkflow.tspersists snapshots and updateslastCheckedAtwithout modifying the already-advanced next check time.
Frequently Asked Questions
How does OpenSEO prevent duplicate scheduled checks?
The system prevents duplicates by immediately updating nextCheckAt in the cron handler before invoking the workflow. According to the source code in src/server/features/rank-tracking/services/scheduledRankChecks.ts, this atomic advancement ensures that even if the workflow fails or retries, the config will not appear in subsequent getDueConfigsWithOrganization queries until the newly calculated time arrives.
What happens if an organization runs out of credits during a scheduled check?
If credit validation fails during RankCheckWorkflow.ts execution, the workflow halts before consuming additional resources. In hosted mode, the estimateRankCheckCredits function checks balances upfront, and the cron skips configs for unpaid organizations entirely. The schedule still advances normally, so the next check occurs at the proper interval once credits are replenished.
Can I schedule rank checks for specific times of day?
No, OpenSEO does not support user-defined check times. The computeNextCheckAt function in src/shared/rank-tracking.ts automatically assigns random hours between 04:00 and 09:00 UTC for monthly checks, and random hours/minutes for daily and weekly intervals. This distributes server load and prevents thundering herds across the Cloudflare Worker infrastructure.
Where is the scheduling logic defined in the codebase?
The core scheduling logic resides in two primary locations. src/shared/rank-tracking.ts contains computeNextCheckAt and isScheduledRankTrackingInterval for date calculations and interval validation. The cron execution logic lives in src/server/features/rank-tracking/services/scheduledRankChecks.ts, which queries due configs and initializes the workflow defined in src/server/workflows/RankCheckWorkflow.ts.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →