DEV Community

goalprediction
goalprediction

Posted on

Automating SEO for Dynamic Sports Data: Google Indexing API & JSON-LD

In my previous post on [https://dev.to/goalprediction/how-we-built-a-real-time-football-analytics-dashboard-with-nuxt-4-and-redis-p3e], I walked through how we handled real-time data caching using Nuxt 4 and Redis for our football analytics platform, GoalPrediction. Today, I want to tackle the other side of dynamic content: SEO and Indexability.

When you are building a sports prediction platform, data changes by the minute. Odds fluctuate, match statuses update, and new predictions are generated. Waiting days or weeks for search engine bots to organically crawl your updated pages is not an option. You need your 1X2, BTTS, and Over/Under prediction pages indexed immediately to capture search traffic before the match kicks off.

Here is how we automated our SEO infrastructure using the Google Indexing API, integrated with our Node.js backend, and structured our data with JSON-LD in Nuxt.

The Problem with Traditional Crawling

Standard sitemap.xml submission works well for static pages (like "About Us" or "Terms of Service"). However, for dynamic match pages:

  • Matches have a short lifespan (pre-match to post-match).
  • Search volumes peak right before kickoff.
  • If Google crawls the page a day late, the event is over, and the traffic opportunity is lost.

The Solution: Google Indexing API

The Google Indexing API allows developers to directly notify Google when pages are added, updated, or removed. This prompts Googlebot to schedule pages for a fresh crawl almost instantly.

Step 1: Setting up the Node.js Service

First, you need a service account from the Google Cloud Console with permissions to use the Web Search Indexing API. Once you have your service-account.json key, you can set up a simple worker in your Node.js backend.


javascript
const { google } = require('googleapis');
const key = require('./service-account.json');

const jwtClient = new google.auth.JWT(
  key.client_email,
  null,
  key.private_key,
  ['[https://www.googleapis.com/auth/indexing](https://www.googleapis.com/auth/indexing)'],
  null
);

async function requestIndexing(url) {
  try {
    const tokens = await jwtClient.authorize();
    const options = {
      url: '[https://indexing.googleapis.com/v3/urlNotifications:publish](https://indexing.googleapis.com/v3/urlNotifications:publish)',
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${tokens.access_token}`,
      },
      data: {
        url: url,
        type: 'URL_UPDATED', // Use URL_DELETED when removing old matches
      },
    };

    const response = await google.indexing().urlNotifications.publish(options);
    console.log(`Successfully requested indexing for: ${url}`);
  } catch (error) {
    console.error('Error requesting indexing:', error);
  }
}

// Example trigger when a new match prediction is generated
requestIndexing('[https://goalprediction.com/predictions/manchester-united-vs-bournemouth-afc-21h00-25-10-2026](https://goalprediction.com/predictions/manchester-united-vs-bournemouth-afc-21h00-25-10-2026)');
Enter fullscreen mode Exit fullscreen mode

Top comments (0)