05 Sept 2026 · 8 min read
Can AI Find Your Work—and Would You Know?
Make your work readable to AI clients, observe when recognized agents request it, and distinguish access from influence. An article followed by the practical Netlify method.
The article — would you know it tried?
You publish an article because you want an idea to reach someone. You give it a useful title, make it easy to read, and perhaps check your analytics to see whether anyone found it.
But the next person to encounter your work might not arrive through your homepage. They might ask an AI assistant a question, and that assistant might fetch your article while preparing an answer. Would you know it tried?
Your browser analytics can show no visitors even while automated clients are requesting your pages. A tracking script cannot report a visit if the client never runs that script. And if your article itself requires JavaScript to render, a client that does not execute JavaScript may receive an application shell instead of the words you wrote.
These are two different blind spots: whether your content is accessible, and whether you can observe requests for it.
Access comes before influence
It is tempting to turn this into another visibility score: how many AI bots visited today, how often a model noticed us, whether our content is becoming more influential. But a request does not support all those conclusions.
A fetch is evidence that a client requested a resource. It does not prove that the article was understood, included in an answer, cited, recommended, or used for training. A crawler’s claimed identity is not proof of who operated it. Nor does a successful HTTP response guarantee that the response contained a useful article.
The useful starting point is more modest: if someone asks an AI about a topic I write about, can it reach my work—and what evidence do I have that it is trying?
That question leads to practical decisions. Repeated failures can reveal an access problem. Requests concentrated on particular articles can suggest where to inspect content quality and freshness. High automated request volumes can justify reviewing resource use. Access patterns can inform whether to welcome or restrict particular clients, although identifying a user agent alone is not an enforcement mechanism.
None of this requires treating bot counts as readership or proof of business value. It is a way to inspect the distribution of your work, with the limits attached.
What I changed on my own website
On darrylwong.me, I addressed both blind spots. First, I made the writing index and published articles available as complete HTML, using the same React components as the interactive site. Readers and crawlers receive the same article; there is no special bot-only version.
Second, I added a small server-side collector for requests carrying recognized AI user-agent names. It records private daily aggregates rather than detailed visitor logs: the reported agent, page, HTTP status, count, and first/last-seen times. It does not store IP addresses, cookies, query strings or raw user-agent strings.
I kept the existing hosting plan and added no analytics subscription. That is a description of this deployment—not a promise that every hosting account can run it without cost.
The result is deliberately limited. I can inspect requests from clients that identify themselves in a supported way. I cannot identify every AI agent, authenticate a provider from its name, or infer citations from a fetch.
The first verification was not evidence of organic AI interest, either. We sent six synthetic requests, confirmed that all six appeared—including one served from cache—and removed those test counts. A separately marked QA request was excluded. The point was to establish that the instrument worked before interpreting anything it might record.
The question worth keeping
Browser analytics remains useful for understanding consented human interactions. Server-side AI access counts answer a different question. Neither replaces the other.
For a small publisher, the value is not another dashboard to watch. It is knowing that the work is available in a readable form, and having a modest, inspectable record when recognized automated clients request it.
Make the work accessible. Observe access honestly. Do not mistake access for influence.
The method — 1. Start from the tested reference
This is a worked implementation for a React/Vite site hosted on Netlify, not a drop-in package for every framework. If your framework already serves complete article HTML, keep that capability and skip the custom prerender step. If you already have suitable request logs, assess whether they answer the question before building another collector.
The source Gist below links to every implementation file at the verified repository commit: renderer, edge collector, shared classification and storage logic, scheduled cleanup, private report and regression tests. Use those complete files rather than an incomplete middleware snippet.
Use a Node version supported by the selected dependencies and preserve the lockfile for reproduction. The reference uses @netlify/blobs for storage and esbuild for its renderer. Adapt the production hostname, public-route allowlist, article source, component imports, metadata and published-slug list. Keep drafts and private routes excluded, and review repository licensing before reusing code.
2. Serve the article before JavaScript runs
The reference build runs Vite, then bundles a Node renderer with esbuild. React’s server renderer and a static router render the existing writing routes. The build inserts that markup into the HTML template, sets per-page metadata and writes an index.html for each published route.
At the original tracking deployment, this generated 21 pages: 20 articles and the writing index. The browser subsequently mounts the existing React application; this is not a claim that the site uses React hydration.
Inspect a built article with JavaScript disabled. Confirm the actual text, heading and canonical URL—not just a successful response or an empty root element. Also test browser navigation, consent controls and mobile layout after JavaScript starts. Reuse the existing content source and test both modes.
3. Collect a narrow class of requests at the edge
Route and user-agent header declarations restrict when the collector is considered. The handler requires the production hostname and GET method, classifies the reported identity and normalizes the path. It uses context.next() to inspect the downstream response and records only HTML responses. Storage work runs through context.waitUntil; errors are caught, with onError set to bypass so collection failures do not take down the content.
The initial classifier recognizes GPTBot, OAI-SearchBot, ChatGPT-User, ClaudeBot, Claude-SearchBot, Claude-User, PerplexityBot and Perplexity-User. It checks token boundaries and excludes marked QA traffic. Maintain this list as provider conventions change; generic browser identities remain outside its coverage.
Do not cache the collector’s own response in a way that skips execution on subsequent visits. Test an actual CDN cache hit rather than assuming origin-request counts equal all edge requests.
The implementation excludes ordinary browsers, assets, admin/login and preview hosts. Unknown article URLs collapse to /posts/:unknown. If a site’s SPA fallback returns an error page with HTTP 200, the record still says 200; inspect content as well as status when diagnosing accessibility.
4. Aggregate privately, with bounded writes
The dedicated site-scoped store is ai-access-v1. Each aggregate represents a UTC date, reported agent, normalized path and HTTP status. Values contain date, agent, path, status, count, firstSeen and lastSeen.
A naive read-increment-write can lose updates when requests overlap. The implementation reads the existing value and ETag, then conditionally creates or updates the record. Conflicts retry up to six times, using strong reads and conditional writes.
Exhausted retries or storage failures may undercount. This is lightweight, best-effort observability, not a billing-grade event ledger. At higher volumes, reassess contention, storage operations and reporting performance.
These minimization choices apply to this collector’s store, not necessarily to the hosting provider’s own infrastructure logs. Disclose server-side collection separately from browser analytics; collecting fewer fields is not, by itself, a blanket privacy-compliance claim.
5. Read the report and expire old aggregates
In the reference checkout, with dependencies installed and the Netlify CLI authenticated, run npx netlify status to check the linked project, then npm run ai:report. The command uses the existing CLI login and prints a private Markdown table. It creates no public dashboard or reporting endpoint. Records are read individually, so runtime grows with the number of keys.
The report includes the current UTC date and the preceding 29 dates. Collection is automatic; generating the report is manual. There is no historical reconstruction and no scheduled email delivery.
Cleanup is configured for 00:20 UTC daily, limited to expired date-prefixed records in the dedicated store. Expired records may remain until cleanup runs, and longer if it fails.
Verification boundary: retention date logic passed automated tests, the schedule was packaged, and direct public invocation returned 403. The first automatic scheduled run was not observed before publication. Verify that run in your own deployment logs before treating live deletion as established.
6. Verify the whole path, not just the code
Run npm run ai:test for identity boundaries, QA exclusions, route normalization, concurrent conditional writes, retention dates and report key handling. Also run the site’s lint, typecheck, consent tests and build before deployment.
On a site you own, record the baseline count, send a small number of recognized test requests, repeat until a response is served from the CDN cache, and confirm the count increased by exactly the eligible requests. A marked request such as GPTBot/1.0 DarrylSiteAudit/1.0 should be excluded.
Remove only isolated synthetic records after checking for concurrent real traffic. Do not delete a shared row merely because your probes contributed to it.
Our production test found one useful integration bug: the CLI listed keys with decoded path separators, while the report initially expected only encoded paths. The collector had stored the requests correctly; the report filtered them out. The correction accepts both representations and has a regression test. An empty report is not enough to establish that nobody visited.
7. Keep cost and rollback explicit
This deployment retained an existing Free Legacy Netlify plan and enabled no new paid subscription. Other accounts and traffic levels may have different economics. Check your actual plan, compute allowances and storage usage; do not migrate billing plans merely to follow this guide.
Each recognized request adds storage work, with extra work on retries. Cleanup and manual reports also consume resources. No separate legacy storage allotment was established during this implementation, so no new subscription should not be read as unlimited free storage.
Keep collector, renderer and cleanup changes in reviewable commits. To roll back, revert relevant changes without overwriting later work, then redeploy. Private aggregates do not disappear merely because the collector is removed. Decide how they will expire before disabling cleanup.
The outcome is not a promise that AI will recommend your work. It is a readable website and a small, private record of supported clients requesting it—a firmer place to start than guessing.
Share and save
Living source
This post is the stable site version. The source gist may be updated as the working pattern develops.
Read the full article and implementation methodInspect the tested implementationRead the delivery verification record