A scan misses pages in a single page app because most links and forms are created by JavaScript after the page loads, and a traditional crawler reads raw HTML. Fix it with a crawler that runs scripts, by importing your routes or API definition, and by checking coverage against a known list.
Why crawlers fall short
A traditional spider follows anchor tags in the HTML it receives. A single page app often returns a nearly empty shell, then builds navigation in the browser. The spider sees the shell and stops. Data calls go to API endpoints the crawler never learned about.
ZAP's documentation notes that its AJAX spider, built for such sites, is no longer the recommended option for crawling modern web apps, and points to its client spider instead. Check the current docs for your version.
Options compared
| Approach | Helps with | Limitation |
|---|---|---|
| Traditional spider | Server-rendered pages | Misses script-built links |
| Browser-driven crawl | Script-built navigation | Slower, needs tuning |
| Import API definition | Backend endpoints | Misses UI-only routes |
| Import recorded traffic | Real user flows | Only what you recorded |
| Seed URL list | Known routes | Needs upkeep |
Steps
- Write down a list of 20 to 30 URLs and API calls you know the app has.
- Run a short crawl and see how many appear in the results.
- Switch to a browser-driven crawler and rerun.
- Import your OpenAPI file or GraphQL schema so backend routes are tested directly.
- Record a manual session of key flows through a proxy and feed it to the scanner.
- Add login, since most routes sit behind it.
- Track coverage as a number you check each release.
Signs of poor coverage
The crawl finishes in seconds. Results contain only the login page and static files. Active scan findings are all in headers and none in parameters.
Keep in mind
Coverage is the limit of any scan. A thousand checks run against five pages test five pages. Spend time on coverage before tuning individual rules.
Try it yourself
Watch which pages the crawler discovers and which routes stay unseen.
Click through it at your own pace, or open it full screen.
Quick check
Is a browser-based crawl always better?
It reaches more script-built routes but runs slower. Combine it with an API import for backend coverage.
How do I know my coverage?
Compare crawled URLs and calls with a hand-written list of known routes. The gap is your blind spot.
Where Safeguard fits
Safeguard offers dependency and SBOM scanning, reachability analysis and policies and gates that can block a pull request, so the libraries behind your app are checked alongside the app itself. You can try a free scan at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.