How to Detect What Framework a Website Uses Programmatically
You're building a competitive intelligence tool, a lead generation pipeline, or just curious about what powers a particular website. You want to know programmatically — not by opening DevTools and guessing — whether a site runs React, Vue, Angular, Next.js, Nuxt, Svelte, or something else entirely.
This guide covers the four main techniques for detecting website frameworks in code, along with their tradeoffs. Then we'll show how StackPeek combines all of them into a single API call.
1. HTTP Header Fingerprinting
The simplest detection method requires nothing more than reading HTTP response headers. Many frameworks and hosting platforms leak identifying information before you even parse the HTML body.
Here's what to look for:
- X-Powered-By: Often reveals Express, Next.js, ASP.NET, or PHP. Next.js sets this to
Next.jsby default unless explicitly removed. - Server: Can identify Nginx, Apache, Cloudflare, or framework-specific servers like Deno Deploy or Bun.
- X-Generator: Used by static site generators like Hugo, Gatsby, and Jekyll.
- Via / X-Cache: Indicates CDN usage — Cloudflare, Fastly, Vercel Edge Network.
import requests
resp = requests.head("https://example.com", allow_redirects=True)
for header in ["x-powered-by", "server", "x-generator"]:
val = resp.headers.get(header)
if val:
print(f"{header}: {val}")
Pros: Fast, lightweight — a HEAD request is all you need. Cons: Easily stripped by production configs. Many sites remove X-Powered-By for security. You'll miss frameworks that don't set custom headers.
2. HTML Pattern Matching
Parsing the HTML source reveals framework-specific patterns that are much harder to remove. Every framework leaves fingerprints in the DOM structure it generates.
| Framework | HTML Fingerprint |
|---|---|
| Next.js | __next div, _next/static script paths |
| Nuxt.js | __nuxt div, _nuxt/ asset paths |
| Gatsby | ___gatsby div, /page-data/ paths |
| Angular | ng-version attribute, ng- prefixed attributes |
| Vue.js | data-v- scoped style attributes |
| Svelte | svelte- class prefixes, __svelte comments |
| WordPress | wp-content/ paths, wp-json links |
import re, requests
html = requests.get("https://example.com").text
patterns = {
"Next.js": [r'id="__next"', r'/_next/static'],
"Nuxt.js": [r'id="__nuxt"', r'/_nuxt/'],
"Angular": [r'ng-version=', r'ng-\w+='],
"Vue.js": [r'data-v-[a-f0-9]'],
"Gatsby": [r'id="___gatsby"'],
}
for fw, rules in patterns.items():
if any(re.search(r, html) for r in rules):
print(f"Detected: {fw}")
Pros: Reliable — these patterns are structural to how frameworks render. Cons: Requires fetching the full HTML body. Won't work on sites behind JavaScript rendering (SPAs that serve an empty shell).
3. JavaScript Global Detection
Many frameworks attach globals to the window object. If you're running a headless browser (Puppeteer, Playwright), you can check for them directly:
- React:
window.__REACT_DEVTOOLS_GLOBAL_HOOK__or_reactRootContaineron DOM nodes - Vue:
window.__VUE__or__vue_app__on root elements - Angular:
window.ngorgetAllAngularRootElements() - Svelte:
window.__sveltein dev mode - jQuery:
window.jQueryorwindow.$
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
const frameworks = await page.evaluate(() => ({
react: !!document.querySelector('[data-reactroot]'),
vue: !!window.__VUE__,
angular: !!window.ng,
jquery: !!window.jQuery
}));
console.log(frameworks);
await browser.close();
})();
Pros: Can detect frameworks on fully client-rendered SPAs. Cons: Requires a headless browser — slow (2-10 seconds per page), resource-heavy, and expensive to scale. Production builds often strip dev-mode globals.
4. Script and Asset Path Analysis
The URLs of JavaScript bundles and static assets contain telltale patterns. Webpack chunk hashes, Vite module paths, and framework-specific directory structures are reliable signals even on heavily optimized production builds.
- Next.js:
/_next/static/chunks/with webpack hashes - Vite-based (Vue, Svelte, React):
/assets/index-[hash].jspatterns - Create React App:
/static/js/main.[hash].js - Angular CLI:
runtime.[hash].js,polyfills.[hash].js,main.[hash].js - Remix:
/build/directory with route-based chunks
Combining script path analysis with HTML patterns gives you high confidence without needing a headless browser. But maintaining a detection rule database across framework versions is a full-time job.
The Easier Way: One API Call with StackPeek
StackPeek combines all four techniques — header fingerprinting, HTML pattern matching, script analysis, and meta tag parsing — into a single API call that returns results in under 500ms. No headless browser needed.
curl "https://stackpeek.web.app/api/v1/detect?url=https://linear.app"
Response:
{
"url": "https://linear.app",
"technologies": [
{ "name": "React", "category": "framework", "confidence": 0.97 },
{ "name": "Next.js", "category": "framework", "confidence": 0.94 },
{ "name": "Vercel", "category": "hosting", "confidence": 0.99 },
{ "name": "Cloudflare", "category": "cdn", "confidence": 0.91 }
],
"scanTime": 287
}
Instead of maintaining your own detection rules, parsing HTML, and running headless browsers, you get structured JSON with confidence scores and categorized results. The free tier gives you 100 scans per day — no API key required.
Try StackPeek free
Detect any website's framework in under 500ms. 100 scans/day, no API key required.
Try the live scanner →When to Build Your Own vs. Use an API
Build your own if: You only need to detect 2-3 specific frameworks, you're already running a headless browser for other reasons, or you need to work entirely offline. The code snippets above are a solid starting point.
Use StackPeek if: You need broad framework coverage (120+ technologies), you care about speed and reliability, you don't want to maintain detection rules as frameworks evolve, or you're scanning at any kind of volume. At $9/month for 5,000 scans, it's cheaper than running your own Puppeteer infrastructure.
Framework detection is a solved problem — the question is whether you want to solve it yourself every time a framework ships a new version, or let an API handle it. Try StackPeek free →