CrawlPodScan your site

Our "framework-agnostic" function only worked in Next.js

Muhammad Faizan · Published August 4, 2026

ai-visibility's detectAndOptimize() was documented as framework-agnostic — an HTML string and a User-Agent string in, transformed HTML out, no request or response object required, works in any runtime. That description was true of the function's signature. It was false of where the function actually lived: ai-visibility/next, a module whose top-level scope statically imports next/server. Importing detectAndOptimize from a Nuxt, Vue, or plain Node project — never calling it, just importing it — crashed with Cannot find module 'next/server' unless next happened to be installed. The bug shipped in 0.3.0 and reached npm before anyone caught it.

The import graph problem

JavaScript module resolution doesn't care what a function's parameters look like. It cares what the module imports at the top level, before any of your code runs. ai-visibility/next looked, from the outside, like this:

// ai-visibility/next — module scope
import { NextRequest, NextFetchEvent, NextResponse } from 'next/server'
import { detectAndOptimize } from './detector' // re-exported for convenience

export function createNextMiddleware(options) { /* ... uses next/server ... */ }
export { detectAndOptimize }

createNextMiddleware genuinely needs next/server — it returns a function typed against NextRequest/NextResponse. detectAndOptimize doesn't touch either. But both were exported from the same module, and Node resolves a module's imports before it resolves which named export you're actually reaching for. There's no such thing as "import half a module" — import { detectAndOptimize } from 'ai-visibility/next' pulls in everything at that module's top level, including the next/server import that only the other export needed. A Nuxt project with no next in its dependency tree hit that import and crashed, on a line that never mentioned Next.js.

This is a case where the JSDoc comment and the README were both correct about the function's contract, and both wrong about a consequence of its location — nobody had asked "does importing this pull in something its description says it doesn't need."

Why it reached npm

At the time, ai-visibility already had a real safeguard against exactly this class of bug: a test that walks the static import graph of ai-visibility/detector and ai-visibility/generators and fails if either one resolves to a runtime dependency. That test would have caught this instantly — if detectAndOptimize had been inside the module it was testing. It wasn't. It lived in ai-visibility/next, a subpath that is allowed to depend on next as an optional peer. The zero-dependency guarantee and the framework-agnostic claim were two different promises, checked by two different mechanisms — one automated, one not — and the function had been placed under the automated one's blind spot.

It was found the ordinary way: building the Nuxt integration recipe for 0.3.2, running the example, and hitting the crash directly rather than assuming the docs were accurate.

The fix, and what actually closes the gap

0.3.1 moved detectAndOptimize into ai-visibility/detector — the zero-dependency module, and its real home — with ai-visibility/next re-exporting it for anyone already importing it from there. No signature changed; no existing ai-visibility/next import broke.

The part worth stating plainly: relocating the function fixed this one instance, but it's the pre-existing zero-dependency import-graph test that now prevents the next version of this bug. detectAndOptimize sits inside the exact module that test already walks. If a future change ever reintroduces a next/server (or any other runtime) import into ai-visibility/detector, CI fails immediately — mechanically, on the import graph itself, not on someone remembering to re-read a doc comment against the actual module boundary. A documented claim is only as reliable as what checks it; moving the code to where an existing, automated check already covered it turned a claim back into a guarantee.

What this means if you're upgrading

If you're on 0.3.0 and importing detectAndOptimize from ai-visibility/next in a non-Next.js project, update the import — the function itself is unchanged:

// Before 0.3.1 — only safe inside a Next.js project
import { detectAndOptimize } from 'ai-visibility/next'

// 0.3.1+ — actually framework-agnostic
import { detectAndOptimize } from 'ai-visibility/detector'

Full detail is in the migration guide; the four framework recipes built afterward — Nuxt, Vue, React, and React Router — are at /docs/recipes.