submitIndexNow function

Future<bool> submitIndexNow({
  1. required String siteBase,
  2. required String key,
  3. required List<String> paths,
  4. Uri? endpoint,
  5. String? keyLocation,
  6. Duration timeout = const Duration(seconds: 10),
})

Submits URLs to search engines via the IndexNow protocol (https://www.indexnow.org — Bing, Seznam, Naver, Yandex share the index; Google crawls the classic way via sitemap).

Instead of waiting days for a recrawl, you push changed pages actively — typically right after a deploy or prerender run:

await submitIndexNow(
  siteBase: 'https://example.com',
  key: 'a1b2c3d4e5f6...',
  paths: ['/', '/blog/neuer-post'],
);

key is a self-chosen hex/alphanumeric string (8–128 chars) that must be reachable as https://example.com/<key>.txt containing the key — seoBotMiddleware(indexNowKey: ...) and prerenderSite(indexNowKey: ...) serve/write that file for you.

Returns true when the endpoint accepted the submission (HTTP 200/202). Network errors surface as exceptions — decide at the call site whether a failed ping may fail the deploy. A hanging endpoint aborts with a TimeoutException after timeout instead of stalling the deploy script forever.

siteBase must be an absolute URL including the scheme (https://example.com) — otherwise throws an ArgumentError.

Implementation

Future<bool> submitIndexNow({
  required String siteBase,
  required String key,
  required List<String> paths,
  Uri? endpoint,
  String? keyLocation,
  Duration timeout = const Duration(seconds: 10),
}) async {
  final base = siteBase.endsWith('/')
      ? siteBase.substring(0, siteBase.length - 1)
      : siteBase;
  final host = Uri.parse(base).host;
  if (host.isEmpty) {
    throw ArgumentError.value(
      siteBase,
      'siteBase',
      'must be an absolute URL including the scheme, e.g. https://example.com',
    );
  }
  if (paths.isEmpty) return true;
  final urls = [
    for (final path in paths.map(normalizeSeoPath))
      path == '/' ? '$base/' : '$base$path',
  ];

  final body = jsonEncode({
    'host': host,
    'key': key,
    'keyLocation': keyLocation ?? '$base/$key.txt',
    'urlList': urls,
  });

  final client = HttpClient()..connectionTimeout = timeout;
  try {
    final request = await client
        .postUrl(endpoint ?? Uri.parse('https://api.indexnow.org/indexnow'))
        .timeout(timeout);
    request.headers.contentType =
        ContentType('application', 'json', charset: 'utf-8');
    request.write(body);
    final response = await request.close().timeout(timeout);
    await response.drain<void>().timeout(timeout);
    return response.statusCode == 200 || response.statusCode == 202;
  } finally {
    // force: ein hängender Socket darf den Prozess nicht offenhalten.
    client.close(force: true);
  }
}