submitIndexNow function
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);
}
}