# How OpenSEO Determines Rank Tracking Results: Rank Group vs Rank Absolute Explained

> Understand OpenSEO rank tracking. Learn how rank group shows your best rank across devices, while rank absolute displays the exact position for a single device.

- Repository: [Every App/open-seo](https://github.com/every-app/open-seo)
- Tags: deep-dive
- Published: 2026-09-01

---

**Rank group shows the best (lowest) rank across Desktop and Mobile when tracking both devices, while rank absolute displays the exact position for a single selected device.**

OpenSEO's rank tracking feature fetches SERP position data from the DataForSEO service, then presents results in one of two display modes depending on your configuration. The `devices` field in your **rank tracking configuration** directly determines whether you see device-specific absolute ranks or a consolidated group rank. This logic is implemented in [`src/shared/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/rank-tracking.ts) and controlled by the `RankTrackingConfig` type defined in [`src/types/schemas/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/rank-tracking.ts).

## How the Devices Setting Controls Rank Display Mode

The rank display mode is not a separate toggle. Instead, it derives automatically from whether you track one device or both.

### Single Device: Rank Absolute Mode

When your configuration specifies a single device—either `"desktop"` or `"mobile"`—OpenSEO operates in **rank absolute** mode. The system records and displays the exact position returned for that specific device only, with no aggregation or comparison.

This setting produces a straightforward, unambiguous result: you see precisely where your keyword ranks on your chosen platform.

### Both Devices: Rank Group Mode

When the configuration's `devices` field equals `"both"`, OpenSEO switches to **rank group** mode. The UI computes the group rank as the minimum of the two absolute ranks—that is, the best (numerically lowest) position achieved across Desktop and Mobile.

This provides a concise, optimistic summary that reflects your keyword's most favorable placement on any device.

## Core Implementation in [`src/shared/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/rank-tracking.ts)

The rank-tracking utilities handle device translation and scheduling. Lines 29-45 define `devicesLabel` and `devicesCount`, which convert the raw `devices` setting into UI-ready values:

```ts
// Conceptual flow based on src/shared/rank-tracking.ts
// These utilities prepare device data for the rendering layer

function getDevicesLabel(devices: 'desktop' | 'mobile' | 'both'): string {
  return devices === 'both' ? 'Desktop + Mobile' 
       : devices === 'desktop' ? 'Desktop' 
       : 'Mobile';
}

function getDevicesCount(devices: 'desktop' | 'mobile' | 'both'): 1 | 2 {
  return devices === 'both' ? 2 : 1;
}

```

The grouping logic itself executes in the client-side component that renders the rank tracking table. When `devicesCount` equals 2, this component selects `Math.min(desktopRank, mobileRank)` as the displayed group rank.

The `computeNextCheckAt` function (lines 78-124) manages crawl scheduling but does not influence the rank group versus rank absolute distinction—it only determines when fresh DataForSEO data is fetched.

## Schema Definition in [`src/types/schemas/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/rank-tracking.ts)

The `RankTrackingConfig` type governs all rank tracking behavior. Its `devices` field accepts three literal values that enforce the binary display mode:

```ts
// src/types/schemas/rank-tracking.ts
export interface RankTrackingConfig {
  keyword: string;
  devices: 'desktop' | 'mobile' | 'both';  // ← Controls rank display mode
  scheduleInterval: 'daily' | 'weekly' | 'monthly';
  depth: number;
  // Additional configuration fields...
}

```

Setting `devices: 'both'` triggers rank group display. Setting either single-device value triggers rank absolute display for that platform.

## Practical Code Examples

### Creating a Configuration for Rank Group Display

```ts
import type { RankTrackingConfig } from '@/types/schemas/rank-tracking';

const groupConfig: RankTrackingConfig = {
  keyword: 'open seo',
  devices: 'both',           // Enables rank group: best of Desktop + Mobile
  scheduleInterval: 'weekly',
  depth: 30,
};

```

### Creating a Configuration for Rank Absolute Display

```ts
import type { RankTrackingConfig } from '@/types/schemas/rank-tracking';

const desktopConfig: RankTrackingConfig = {
  keyword: 'open seo',
  devices: 'desktop',        // Shows Desktop rank only
  scheduleInterval: 'weekly',
  depth: 30,
};

```

### Rendering Ranks with Conditional Logic

```ts
function renderRank(
  desktopRank: number | null,
  mobileRank: number | null,
  config: RankTrackingConfig
): string {
  if (config.devices === 'both') {
    // Rank group: lowest number is best position
    const ranks = [desktopRank, mobileRank].filter((r): r is number => r !== null);
    const groupRank = Math.min(...ranks);
    return `#${groupRank} (group)`;
  }
  
  // Rank absolute: single device result
  const absoluteRank = config.devices === 'desktop' ? desktopRank : mobileRank;
  return absoluteRank !== null ? `#${absoluteRank}` : 'Not ranked';
}

```

### Scheduling the Next Crawl

```ts
import { computeNextCheckAt } from '@/shared/rank-tracking';

// Returns ISO timestamp for next DataForSEO data fetch
const nextCheck = computeNextCheckAt('weekly');
// Example: '2024-01-15T09:00:00.000Z'

```

## File Reference Summary

| File | Purpose |
|------|---------|
| [`src/shared/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/rank-tracking.ts) | Device handling, cost estimation, scheduling (`computeNextCheckAt`), and label generation (`devicesLabel`, `devicesCount`) |
| [`src/types/schemas/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/rank-tracking.ts) | `RankTrackingConfig` type definition including the `devices` field that determines rank display mode |
| `src/components/.../rank-table.*` | UI component implementing the `Math.min()` grouping logic based on `devicesCount` |

## Summary

- **Rank absolute** displays exact position for one device; triggered by `devices: 'desktop'` or `devices: 'mobile'`.
- **Rank group** shows the best rank across both devices; triggered by `devices: 'both'`.
- The display mode derives from `RankTrackingConfig.devices` as defined in [`src/types/schemas/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/types/schemas/rank-tracking.ts).
- Device translation utilities in [`src/shared/rank-tracking.ts`](https://github.com/every-app/open-seo/blob/main/src/shared/rank-tracking.ts) (lines 29-45) prepare values for the UI layer.
- The grouping calculation uses `Math.min(desktopRank, mobileRank)` in the rank table rendering component when `devicesCount === 2`.

## Frequently Asked Questions

### How do I switch between rank group and rank absolute in OpenSEO?

Change your rank tracking configuration's `devices` field. Set it to `'both'` for rank group (best position across Desktop and Mobile), or to `'desktop'` or `'mobile'` for rank absolute (single-device position). Update the configuration in your project settings or via the API.

### Does rank group always show the better of the two ranks?

Yes. Rank group explicitly computes `Math.min(desktopRank, mobileRank)`, selecting the numerically lower value—which represents the better SERP position. If your keyword ranks #5 on Desktop and #12 on Mobile, the group rank displays as #5.

### What happens if a keyword has no rank on one device?

The grouping logic filters out null ranks before applying `Math.min()`. If your keyword ranks #8 on Desktop but is unranked on Mobile, the group rank shows #8. Both ranks must be null for the result to indicate no ranking.

### Is the rank group calculation performed server-side or client-side?

The grouping occurs in the client-side component that renders the rank tracking table. The server stores and returns both absolute ranks; the UI layer applies the `Math.min()` logic based on the configuration's `devicesCount` value to determine what to display.