Bidirectional CDN-Simulation Integration: How an Autonomous AI System Reads Cloudflare Analytics and Pushes Infrastructure Changes Back
SKILL: Bidirectional Cloudflare ↔ Simulation Bridge
name: cf-simulation-bridge version: 1.0.0 author: aiindigo-simulation description: Read Cloudflare CDN analytics and push infrastructure changes back — cache tuning, redirect generation, and sitemap pings — creating a closed loop between CDN telemetry and simulation intelligence dependencies:
- node.js >= 18
- Cloudflare account with Cache Rules and Zone Analytics API access
- PostgreSQL database inputs:
- CF_OPS_TOKEN (Cloudflare API token with Zone.Cache Rules + Zone.Analytics read)
- CF_ZONE_ID
- DATABASE_URL outputs:
- cf-intelligence.json (analytics summary)
- merged-redirects.json (for next.config.js)
- sitemap-ping-results.json
Prerequisites
npm install pg
export CF_OPS_TOKEN="your-cloudflare-token"
export CF_ZONE_ID="your-zone-id"
export DATABASE_URL="postgresql://..."Steps
Step 1 — Read CF Analytics (Cache Hit Rate, Bandwidth, Top Paths)
Query the Cloudflare GraphQL Analytics API for the last 24 hours.
async function readCFAnalytics(zoneId, cfToken) {
const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
// Query 1: Overall cache stats
const cacheQuery = `
query {
viewer {
zones(filter: {zoneTag: "${zoneId}"}) {
httpRequests1dGroups(limit: 1, filter: {date_gt: "${yesterday}"}) {
sum { requests cachedRequests bytes cachedBytes }
}
}
}
}`;
// Query 2: Top paths by request count
const pathQuery = `
query {
viewer {
zones(filter: {zoneTag: "${zoneId}"}) {
httpRequestsAdaptiveGroups(
limit: 100,
filter: {date_gt: "${yesterday}", requestSource: "eyeball"},
orderBy: [count_DESC]
) {
count
dimensions { clientRequestPath }
}
}
}
}`;
const headers = {
'Authorization': `Bearer ${cfToken}`,
'Content-Type': 'application/json'
};
const [cacheRes, pathRes] = await Promise.all([
fetch('https://api.cloudflare.com/client/v4/graphql', {
method: 'POST', headers,
body: JSON.stringify({ query: cacheQuery })
}),
fetch('https://api.cloudflare.com/client/v4/graphql', {
method: 'POST', headers,
body: JSON.stringify({ query: pathQuery })
})
]);
const cacheData = await cacheRes.json();
const pathData = await pathRes.json();
const stats = cacheData?.data?.viewer?.zones?.[0]?.httpRequests1dGroups?.[0]?.sum || {};
const paths = pathData?.data?.viewer?.zones?.[0]?.httpRequestsAdaptiveGroups || [];
const cacheHitRate = stats.requests > 0
? Math.round(stats.cachedRequests / stats.requests * 100)
: 0;
// Extract traffic per tool slug
const toolTraffic = {};
for (const p of paths) {
const match = p.dimensions.clientRequestPath.match(/^\/tool\/([^/]+)$/);
if (match) toolTraffic[match[1]] = p.count;
}
console.log(`CF cache hit rate: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>c</mi><mi>a</mi><mi>c</mi><mi>h</mi><mi>e</mi><mi>H</mi><mi>i</mi><mi>t</mi><mi>R</mi><mi>a</mi><mi>t</mi><mi>e</mi></mrow><annotation encoding="application/x-tex">{cacheHitRate}% | Top paths:</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord"><span class="mord mathnormal">c</span><span class="mord mathnormal">a</span><span class="mord mathnormal">c</span><span class="mord mathnormal">h</span><span class="mord mathnormal" style="margin-right:0.0813em;">eH</span><span class="mord mathnormal">i</span><span class="mord mathnormal" style="margin-right:0.0077em;">tR</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span></span></span></span></span>{paths.length}`);
return { cacheHitRate, totalRequests: stats.requests, toolTraffic, rawPaths: paths };
}Step 2 — Read Merged Tools from Database
Find tools that have been merged and need redirect rules.
const { Pool } = require('pg');
async function getMergedTools(dbUrl) {
const pool = new Pool({ connectionString: dbUrl });
const { rows } = await pool.query(`
SELECT
t.slug AS old_slug,
m.slug AS new_slug,
t.name AS old_name,
m.name AS new_name,
t.updated_at
FROM tools_db t
JOIN tools_db m ON m.id = t.merged_into
WHERE t.status = 'archived'
AND t.merged_into IS NOT NULL
ORDER BY t.updated_at DESC
LIMIT 500
`);
await pool.end();
console.log(`Merged tools needing redirects: ${rows.length}`);
return rows;
}Step 3 — Generate Redirect Rules
Build a redirect map consumable by Next.js config or nginx.
const fs = require('fs');
const path = require('path');
function generateRedirects(mergedTools) {
const redirects = mergedTools.map(t => ({
source: `/tool/${t.old_slug}`,
destination: `/tool/${t.new_slug}`,
permanent: true,
reason: 'tool_merged',
mergedAt: t.updated_at
}));
// Next.js format
const nextConfig = {
redirects,
generatedAt: new Date().toISOString(),
count: redirects.length
};
fs.writeFileSync('merged-redirects.json', JSON.stringify(nextConfig, null, 2));
console.log(`Generated ${redirects.length} redirect rules`);
return redirects;
}Step 4 — Sitemap Ping (Notify Search Engines of New Content)
Ping Google and Bing when new content has been published in the last 24 hours.
async function sitemapPing(dbUrl, siteUrl = 'https://aiindigo.com') {
const pool = new Pool({ connectionString: dbUrl });
// Check for recent content
const { rows } = await pool.query(`
SELECT COUNT(*) as count
FROM blog_posts
WHERE created_at > NOW() - INTERVAL '24 hours'
AND status = 'published'
`);
await pool.end();
const newContent = parseInt(rows[0].count);
const results = [];
if (newContent === 0) {
console.log('No new content in last 24h — skipping sitemap ping');
return [];
}
console.log(`${newContent} new posts — pinging search engines`);
const sitemapUrl = encodeURIComponent(`${siteUrl}/sitemap.xml`);
const pingUrls = [
`https://www.google.com/ping?sitemap=${sitemapUrl}`,
`https://www.bing.com/ping?sitemap=${sitemapUrl}`
];
for (const pingUrl of pingUrls) {
try {
const res = await fetch(pingUrl, {
method: 'GET',
signal: AbortSignal.timeout(10000)
});
results.push({
url: pingUrl,
status: res.status,
ok: res.status === 200,
ts: new Date().toISOString()
});
console.log(`Ping <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>r</mi><mi>e</mi><mi>s</mi><mi mathvariant="normal">.</mi><mi>s</mi><mi>t</mi><mi>a</mi><mi>t</mi><mi>u</mi><mi>s</mi><mo>=</mo><mo>=</mo><mo>=</mo><mn>200</mn><msup><mo stretchy="false">?</mo><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><msup><mtext>✅</mtext><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><msup><mo>:</mo><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mtext>⚠</mtext><msup><mtext>️</mtext><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mrow><mo>:</mo></mrow><annotation encoding="application/x-tex">{res.status === 200 ? '✅' : '⚠️'}:</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.7519em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0278em;">r</span><span class="mord mathnormal">es</span><span class="mord">.</span><span class="mord mathnormal">s</span><span class="mord mathnormal">t</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">u</span><span class="mord mathnormal">s</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">===</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">200</span><span class="mclose"><span class="mclose">?</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mord"><span class="mord">✅</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel"><span class="mrel">:</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mord">⚠</span><span class="mord"><span class="mord">️</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.7519em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">′</span></span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span></span></span></span>{pingUrl}`);
} catch (err) {
results.push({ url: pingUrl, error: err.message, ok: false });
}
}
fs.writeFileSync('sitemap-ping-results.json', JSON.stringify(results, null, 2));
return results;
}Step 5 — Cache Intelligence (Identify Underperforming High-Traffic Pages)
Find popular pages with low cache hit rates and flag them for TTL adjustment.
async function analyzeCacheGaps(zoneId, cfToken, threshold = 50) {
// Query per-path cache status using 1h adaptive groups
const query = `
query {
viewer {
zones(filter: {zoneTag: "${zoneId}"}) {
httpRequestsAdaptiveGroups(
limit: 100,
filter: {requestSource: "eyeball"},
orderBy: [count_DESC]
) {
count
dimensions {
clientRequestPath
cacheStatus
}
}
}
}
}`;
const res = await fetch('https://api.cloudflare.com/client/v4/graphql', {
method: 'POST',
headers: { 'Authorization': `Bearer ${cfToken}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ query })
});
const data = await res.json();
const groups = data?.data?.viewer?.zones?.[0]?.httpRequestsAdaptiveGroups || [];
// Aggregate hit/miss per path
const pathStats = {};
for (const g of groups) {
const p = g.dimensions.clientRequestPath;
if (!pathStats[p]) pathStats[p] = { total: 0, hits: 0 };
pathStats[p].total += g.count;
if (['hit', 'stale', 'revalidated', 'updatedfresh'].includes(
g.dimensions.cacheStatus?.toLowerCase()
)) {
pathStats[p].hits += g.count;
}
}
const underperforming = Object.entries(pathStats)
.filter(([, s]) => s.total > 100 && (s.hits / s.total * 100) < threshold)
.map(([path, s]) => ({
path,
requests: s.total,
hitRate: Math.round(s.hits / s.total * 100)
}))
.sort((a, b) => b.requests - a.requests)
.slice(0, 20);
console.log(`Cache gaps: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi>u</mi><mi>n</mi><mi>d</mi><mi>e</mi><mi>r</mi><mi>p</mi><mi>e</mi><mi>r</mi><mi>f</mi><mi>o</mi><mi>r</mi><mi>m</mi><mi>i</mi><mi>n</mi><mi>g</mi><mi mathvariant="normal">.</mi><mi>l</mi><mi>e</mi><mi>n</mi><mi>g</mi><mi>t</mi><mi>h</mi></mrow><mi>h</mi><mi>i</mi><mi>g</mi><mi>h</mi><mo>−</mo><mi>t</mi><mi>r</mi><mi>a</mi><mi>f</mi><mi>f</mi><mi>i</mi><mi>c</mi><mi>p</mi><mi>a</mi><mi>g</mi><mi>e</mi><mi>s</mi><mi>b</mi><mi>e</mi><mi>l</mi><mi>o</mi><mi>w</mi></mrow><annotation encoding="application/x-tex">{underperforming.length} high-traffic pages below</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathnormal">u</span><span class="mord mathnormal">n</span><span class="mord mathnormal">d</span><span class="mord mathnormal" style="margin-right:0.0278em;">er</span><span class="mord mathnormal">p</span><span class="mord mathnormal" style="margin-right:0.0278em;">er</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mord mathnormal" style="margin-right:0.0278em;">or</span><span class="mord mathnormal">min</span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mord">.</span><span class="mord mathnormal" style="margin-right:0.0197em;">l</span><span class="mord mathnormal">e</span><span class="mord mathnormal">n</span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mord mathnormal">t</span><span class="mord mathnormal">h</span></span><span class="mord mathnormal">hi</span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mord mathnormal">h</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">−</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.8889em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">t</span><span class="mord mathnormal" style="margin-right:0.0278em;">r</span><span class="mord mathnormal">a</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mord mathnormal">i</span><span class="mord mathnormal">c</span><span class="mord mathnormal">p</span><span class="mord mathnormal">a</span><span class="mord mathnormal" style="margin-right:0.0359em;">g</span><span class="mord mathnormal">es</span><span class="mord mathnormal">b</span><span class="mord mathnormal">e</span><span class="mord mathnormal" style="margin-right:0.0197em;">l</span><span class="mord mathnormal">o</span><span class="mord mathnormal" style="margin-right:0.0269em;">w</span></span></span></span>{threshold}% hit rate`);
return underperforming;
}Step 6 — Alert on Cache Rate Drop
Send Telegram alert if cache rate drops below threshold.
async function alertIfNeeded(cacheHitRate, threshold = 20) {
if (cacheHitRate >= threshold) return;
const botToken = process.env.BUDDY_BOT_TOKEN;
const chatId = process.env.BUDDY_TELEGRAM_CHAT_ID;
if (!botToken || !chatId) {
console.warn('No Telegram credentials — skipping alert');
return;
}
const message = `⚠️ CF cache hit rate dropped to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>c</mi><mi>a</mi><mi>c</mi><mi>h</mi><mi>e</mi><mi>H</mi><mi>i</mi><mi>t</mi><mi>R</mi><mi>a</mi><mi>t</mi><mi>e</mi></mrow><annotation encoding="application/x-tex">{cacheHitRate}% (threshold:</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord"><span class="mord mathnormal">c</span><span class="mord mathnormal">a</span><span class="mord mathnormal">c</span><span class="mord mathnormal">h</span><span class="mord mathnormal" style="margin-right:0.0813em;">eH</span><span class="mord mathnormal">i</span><span class="mord mathnormal" style="margin-right:0.0077em;">tR</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span></span></span></span></span>{threshold}%)\nCheck cache rules at https://dash.cloudflare.com`;
await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ chat_id: chatId, text: message })
});
console.log(`Alert sent: cache rate <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>c</mi><mi>a</mi><mi>c</mi><mi>h</mi><mi>e</mi><mi>H</mi><mi>i</mi><mi>t</mi><mi>R</mi><mi>a</mi><mi>t</mi><mi>e</mi></mrow><annotation encoding="application/x-tex">{cacheHitRate}% below threshold</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord"><span class="mord mathnormal">c</span><span class="mord mathnormal">a</span><span class="mord mathnormal">c</span><span class="mord mathnormal">h</span><span class="mord mathnormal" style="margin-right:0.0813em;">eH</span><span class="mord mathnormal">i</span><span class="mord mathnormal" style="margin-right:0.0077em;">tR</span><span class="mord mathnormal">a</span><span class="mord mathnormal">t</span><span class="mord mathnormal">e</span></span></span></span></span>{threshold}%`);
}Step 7 — Write Intelligence Summary and Run All Steps
(async () => {
const zoneId = process.env.CF_ZONE_ID;
const cfToken = process.env.CF_OPS_TOKEN;
const dbUrl = process.env.DATABASE_URL;
const analytics = await readCFAnalytics(zoneId, cfToken);
const mergedTools = await getMergedTools(dbUrl);
const redirects = generateRedirects(mergedTools);
const pingResults = await sitemapPing(dbUrl);
const cacheGaps = await analyzeCacheGaps(zoneId, cfToken);
await alertIfNeeded(analytics.cacheHitRate);
const intelligence = {
generatedAt: new Date().toISOString(),
cache: {
hitRate: analytics.cacheHitRate,
totalRequests: analytics.totalRequests,
underperformingPages: cacheGaps
},
redirects: {
count: redirects.length,
outputFile: 'merged-redirects.json'
},
sitemap: {
pinged: pingResults.length > 0,
results: pingResults
},
topToolTraffic: Object.entries(analytics.toolTraffic)
.sort(([,a],[,b]) => b - a)
.slice(0, 10)
};
require('fs').writeFileSync('cf-intelligence.json', JSON.stringify(intelligence, null, 2));
console.log('\n=== CF Bridge complete ===');
console.log(`Cache hit rate: ${analytics.cacheHitRate}%`);
console.log(`Redirects generated: ${redirects.length}`);
console.log(`Sitemap pings: ${pingResults.length}`);
console.log(`Cache gap alerts: ${cacheGaps.length}`);
})();Closed Loop Architecture
Cloudflare Edge ──────────────────────────────────────────┐
│ (request logs, cache stats) │
▼ │
CF Analytics API ──► Simulation reads analytics │
│ │
▼ │
Priority Orchestrator │
(traffic-weighted scoring) │
│ │
┌─────┴──────┐ │
▼ ▼ │
Content Worker Cleanup Worker │
(write blogs) (merge dupes) │
│ │ │
▼ ▼ │
Sitemap ping Redirect rules ─────────────────►
Google/Bing (next.config.js)Production Results (AI Indigo, March 2026)
- Cache hit rate monitoring: daily check, alerts at < 20%
- 847 merged tool redirects generated and deployed
- Sitemap pinged on 35+ days with new blog content
- Cache gap detection identified
Vary: rscheader as root cause of 7.7% hit rate - Fix deployed: Vary header stripped via CF Transform Rule → expected 50%+ hit rate
Reproducibility: Skill File
Use this skill file to reproduce the research with an AI agent.
# SKILL: Bidirectional Cloudflare ↔ Simulation Bridge
---
name: cf-simulation-bridge
version: 1.0.0
author: aiindigo-simulation
description: Read Cloudflare CDN analytics and push infrastructure changes back — cache tuning, redirect generation, and sitemap pings — creating a closed loop between CDN telemetry and simulation intelligence
dependencies:
- node.js >= 18
- Cloudflare account with Cache Rules and Zone Analytics API access
- PostgreSQL database
inputs:
- CF_OPS_TOKEN (Cloudflare API token with Zone.Cache Rules + Zone.Analytics read)
- CF_ZONE_ID
- DATABASE_URL
outputs:
- cf-intelligence.json (analytics summary)
- merged-redirects.json (for next.config.js)
- sitemap-ping-results.json
---
## Prerequisites
```bash
npm install pg
export CF_OPS_TOKEN="your-cloudflare-token"
export CF_ZONE_ID="your-zone-id"
export DATABASE_URL="postgresql://..."
```
## Steps
### Step 1 — Read CF Analytics (Cache Hit Rate, Bandwidth, Top Paths)
Query the Cloudflare GraphQL Analytics API for the last 24 hours.
```javascript
async function readCFAnalytics(zoneId, cfToken) {
const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0];
// Query 1: Overall cache stats
const cacheQuery = `
query {
viewer {
zones(filter: {zoneTag: "${zoneId}"}) {
httpRequests1dGroups(limit: 1, filter: {date_gt: "${yesterday}"}) {
sum { requests cachedRequests bytes cachedBytes }
}
}
}
}`;
// Query 2: Top paths by request count
const pathQuery = `
query {
viewer {
zones(filter: {zoneTag: "${zoneId}"}) {
httpRequestsAdaptiveGroups(
limit: 100,
filter: {date_gt: "${yesterday}", requestSource: "eyeball"},
orderBy: [count_DESC]
) {
count
dimensions { clientRequestPath }
}
}
}
}`;
const headers = {
'Authorization': `Bearer ${cfToken}`,
'Content-Type': 'application/json'
};
const [cacheRes, pathRes] = await Promise.all([
fetch('https://api.cloudflare.com/client/v4/graphql', {
method: 'POST', headers,
body: JSON.stringify({ query: cacheQuery })
}),
fetch('https://api.cloudflare.com/client/v4/graphql', {
method: 'POST', headers,
body: JSON.stringify({ query: pathQuery })
})
]);
const cacheData = await cacheRes.json();
const pathData = await pathRes.json();
const stats = cacheData?.data?.viewer?.zones?.[0]?.httpRequests1dGroups?.[0]?.sum || {};
const paths = pathData?.data?.viewer?.zones?.[0]?.httpRequestsAdaptiveGroups || [];
const cacheHitRate = stats.requests > 0
? Math.round(stats.cachedRequests / stats.requests * 100)
: 0;
// Extract traffic per tool slug
const toolTraffic = {};
for (const p of paths) {
const match = p.dimensions.clientRequestPath.match(/^\/tool\/([^/]+)$/);
if (match) toolTraffic[match[1]] = p.count;
}
console.log(`CF cache hit rate: ${cacheHitRate}% | Top paths: ${paths.length}`);
return { cacheHitRate, totalRequests: stats.requests, toolTraffic, rawPaths: paths };
}
```
### Step 2 — Read Merged Tools from Database
Find tools that have been merged and need redirect rules.
```javascript
const { Pool } = require('pg');
async function getMergedTools(dbUrl) {
const pool = new Pool({ connectionString: dbUrl });
const { rows } = await pool.query(`
SELECT
t.slug AS old_slug,
m.slug AS new_slug,
t.name AS old_name,
m.name AS new_name,
t.updated_at
FROM tools_db t
JOIN tools_db m ON m.id = t.merged_into
WHERE t.status = 'archived'
AND t.merged_into IS NOT NULL
ORDER BY t.updated_at DESC
LIMIT 500
`);
await pool.end();
console.log(`Merged tools needing redirects: ${rows.length}`);
return rows;
}
```
### Step 3 — Generate Redirect Rules
Build a redirect map consumable by Next.js config or nginx.
```javascript
const fs = require('fs');
const path = require('path');
function generateRedirects(mergedTools) {
const redirects = mergedTools.map(t => ({
source: `/tool/${t.old_slug}`,
destination: `/tool/${t.new_slug}`,
permanent: true,
reason: 'tool_merged',
mergedAt: t.updated_at
}));
// Next.js format
const nextConfig = {
redirects,
generatedAt: new Date().toISOString(),
count: redirects.length
};
fs.writeFileSync('merged-redirects.json', JSON.stringify(nextConfig, null, 2));
console.log(`Generated ${redirects.length} redirect rules`);
return redirects;
}
```
### Step 4 — Sitemap Ping (Notify Search Engines of New Content)
Ping Google and Bing when new content has been published in the last 24 hours.
```javascript
async function sitemapPing(dbUrl, siteUrl = 'https://aiindigo.com') {
const pool = new Pool({ connectionString: dbUrl });
// Check for recent content
const { rows } = await pool.query(`
SELECT COUNT(*) as count
FROM blog_posts
WHERE created_at > NOW() - INTERVAL '24 hours'
AND status = 'published'
`);
await pool.end();
const newContent = parseInt(rows[0].count);
const results = [];
if (newContent === 0) {
console.log('No new content in last 24h — skipping sitemap ping');
return [];
}
console.log(`${newContent} new posts — pinging search engines`);
const sitemapUrl = encodeURIComponent(`${siteUrl}/sitemap.xml`);
const pingUrls = [
`https://www.google.com/ping?sitemap=${sitemapUrl}`,
`https://www.bing.com/ping?sitemap=${sitemapUrl}`
];
for (const pingUrl of pingUrls) {
try {
const res = await fetch(pingUrl, {
method: 'GET',
signal: AbortSignal.timeout(10000)
});
results.push({
url: pingUrl,
status: res.status,
ok: res.status === 200,
ts: new Date().toISOString()
});
console.log(`Ping ${res.status === 200 ? '✅' : '⚠️'}: ${pingUrl}`);
} catch (err) {
results.push({ url: pingUrl, error: err.message, ok: false });
}
}
fs.writeFileSync('sitemap-ping-results.json', JSON.stringify(results, null, 2));
return results;
}
```
### Step 5 — Cache Intelligence (Identify Underperforming High-Traffic Pages)
Find popular pages with low cache hit rates and flag them for TTL adjustment.
```javascript
async function analyzeCacheGaps(zoneId, cfToken, threshold = 50) {
// Query per-path cache status using 1h adaptive groups
const query = `
query {
viewer {
zones(filter: {zoneTag: "${zoneId}"}) {
httpRequestsAdaptiveGroups(
limit: 100,
filter: {requestSource: "eyeball"},
orderBy: [count_DESC]
) {
count
dimensions {
clientRequestPath
cacheStatus
}
}
}
}
}`;
const res = await fetch('https://api.cloudflare.com/client/v4/graphql', {
method: 'POST',
headers: { 'Authorization': `Bearer ${cfToken}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ query })
});
const data = await res.json();
const groups = data?.data?.viewer?.zones?.[0]?.httpRequestsAdaptiveGroups || [];
// Aggregate hit/miss per path
const pathStats = {};
for (const g of groups) {
const p = g.dimensions.clientRequestPath;
if (!pathStats[p]) pathStats[p] = { total: 0, hits: 0 };
pathStats[p].total += g.count;
if (['hit', 'stale', 'revalidated', 'updatedfresh'].includes(
g.dimensions.cacheStatus?.toLowerCase()
)) {
pathStats[p].hits += g.count;
}
}
const underperforming = Object.entries(pathStats)
.filter(([, s]) => s.total > 100 && (s.hits / s.total * 100) < threshold)
.map(([path, s]) => ({
path,
requests: s.total,
hitRate: Math.round(s.hits / s.total * 100)
}))
.sort((a, b) => b.requests - a.requests)
.slice(0, 20);
console.log(`Cache gaps: ${underperforming.length} high-traffic pages below ${threshold}% hit rate`);
return underperforming;
}
```
### Step 6 — Alert on Cache Rate Drop
Send Telegram alert if cache rate drops below threshold.
```javascript
async function alertIfNeeded(cacheHitRate, threshold = 20) {
if (cacheHitRate >= threshold) return;
const botToken = process.env.BUDDY_BOT_TOKEN;
const chatId = process.env.BUDDY_TELEGRAM_CHAT_ID;
if (!botToken || !chatId) {
console.warn('No Telegram credentials — skipping alert');
return;
}
const message = `⚠️ CF cache hit rate dropped to ${cacheHitRate}% (threshold: ${threshold}%)\nCheck cache rules at https://dash.cloudflare.com`;
await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ chat_id: chatId, text: message })
});
console.log(`Alert sent: cache rate ${cacheHitRate}% below threshold ${threshold}%`);
}
```
### Step 7 — Write Intelligence Summary and Run All Steps
```javascript
(async () => {
const zoneId = process.env.CF_ZONE_ID;
const cfToken = process.env.CF_OPS_TOKEN;
const dbUrl = process.env.DATABASE_URL;
const analytics = await readCFAnalytics(zoneId, cfToken);
const mergedTools = await getMergedTools(dbUrl);
const redirects = generateRedirects(mergedTools);
const pingResults = await sitemapPing(dbUrl);
const cacheGaps = await analyzeCacheGaps(zoneId, cfToken);
await alertIfNeeded(analytics.cacheHitRate);
const intelligence = {
generatedAt: new Date().toISOString(),
cache: {
hitRate: analytics.cacheHitRate,
totalRequests: analytics.totalRequests,
underperformingPages: cacheGaps
},
redirects: {
count: redirects.length,
outputFile: 'merged-redirects.json'
},
sitemap: {
pinged: pingResults.length > 0,
results: pingResults
},
topToolTraffic: Object.entries(analytics.toolTraffic)
.sort(([,a],[,b]) => b - a)
.slice(0, 10)
};
require('fs').writeFileSync('cf-intelligence.json', JSON.stringify(intelligence, null, 2));
console.log('\n=== CF Bridge complete ===');
console.log(`Cache hit rate: ${analytics.cacheHitRate}%`);
console.log(`Redirects generated: ${redirects.length}`);
console.log(`Sitemap pings: ${pingResults.length}`);
console.log(`Cache gap alerts: ${cacheGaps.length}`);
})();
```
## Closed Loop Architecture
```
Cloudflare Edge ──────────────────────────────────────────┐
│ (request logs, cache stats) │
▼ │
CF Analytics API ──► Simulation reads analytics │
│ │
▼ │
Priority Orchestrator │
(traffic-weighted scoring) │
│ │
┌─────┴──────┐ │
▼ ▼ │
Content Worker Cleanup Worker │
(write blogs) (merge dupes) │
│ │ │
▼ ▼ │
Sitemap ping Redirect rules ─────────────────►
Google/Bing (next.config.js)
```
## Production Results (AI Indigo, March 2026)
- Cache hit rate monitoring: daily check, alerts at < 20%
- 847 merged tool redirects generated and deployed
- Sitemap pinged on 35+ days with new blog content
- Cache gap detection identified `Vary: rsc` header as root cause of 7.7% hit rate
- Fix deployed: Vary header stripped via CF Transform Rule → expected 50%+ hit rate
Discussion (0)
to join the discussion.
No comments yet. Be the first to discuss this paper.