vercel-seo-audit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +208 -0
  2. package/bin/vercel-seo-audit.js +2 -0
  3. package/dist/audit/favicon.d.ts +3 -0
  4. package/dist/audit/favicon.d.ts.map +1 -0
  5. package/dist/audit/favicon.js +73 -0
  6. package/dist/audit/favicon.js.map +1 -0
  7. package/dist/audit/index.d.ts +7 -0
  8. package/dist/audit/index.d.ts.map +1 -0
  9. package/dist/audit/index.js +7 -0
  10. package/dist/audit/index.js.map +1 -0
  11. package/dist/audit/metadata.d.ts +3 -0
  12. package/dist/audit/metadata.d.ts.map +1 -0
  13. package/dist/audit/metadata.js +183 -0
  14. package/dist/audit/metadata.js.map +1 -0
  15. package/dist/audit/nextjs.d.ts +3 -0
  16. package/dist/audit/nextjs.d.ts.map +1 -0
  17. package/dist/audit/nextjs.js +104 -0
  18. package/dist/audit/nextjs.js.map +1 -0
  19. package/dist/audit/redirects.d.ts +3 -0
  20. package/dist/audit/redirects.d.ts.map +1 -0
  21. package/dist/audit/redirects.js +134 -0
  22. package/dist/audit/redirects.js.map +1 -0
  23. package/dist/audit/robots.d.ts +3 -0
  24. package/dist/audit/robots.d.ts.map +1 -0
  25. package/dist/audit/robots.js +115 -0
  26. package/dist/audit/robots.js.map +1 -0
  27. package/dist/audit/sitemap.d.ts +3 -0
  28. package/dist/audit/sitemap.d.ts.map +1 -0
  29. package/dist/audit/sitemap.js +152 -0
  30. package/dist/audit/sitemap.js.map +1 -0
  31. package/dist/cli.d.ts +2 -0
  32. package/dist/cli.d.ts.map +1 -0
  33. package/dist/cli.js +51 -0
  34. package/dist/cli.js.map +1 -0
  35. package/dist/constants.d.ts +11 -0
  36. package/dist/constants.d.ts.map +1 -0
  37. package/dist/constants.js +11 -0
  38. package/dist/constants.js.map +1 -0
  39. package/dist/runner.d.ts +6 -0
  40. package/dist/runner.d.ts.map +1 -0
  41. package/dist/runner.js +55 -0
  42. package/dist/runner.js.map +1 -0
  43. package/dist/types.d.ts +53 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +2 -0
  46. package/dist/types.js.map +1 -0
  47. package/dist/utils/html-parser.d.ts +15 -0
  48. package/dist/utils/html-parser.d.ts.map +1 -0
  49. package/dist/utils/html-parser.js +65 -0
  50. package/dist/utils/html-parser.js.map +1 -0
  51. package/dist/utils/http.d.ts +14 -0
  52. package/dist/utils/http.d.ts.map +1 -0
  53. package/dist/utils/http.js +88 -0
  54. package/dist/utils/http.js.map +1 -0
  55. package/dist/utils/index.d.ts +6 -0
  56. package/dist/utils/index.d.ts.map +1 -0
  57. package/dist/utils/index.js +6 -0
  58. package/dist/utils/index.js.map +1 -0
  59. package/dist/utils/output.d.ts +4 -0
  60. package/dist/utils/output.d.ts.map +1 -0
  61. package/dist/utils/output.js +72 -0
  62. package/dist/utils/output.js.map +1 -0
  63. package/dist/utils/url.d.ts +10 -0
  64. package/dist/utils/url.d.ts.map +1 -0
  65. package/dist/utils/url.js +51 -0
  66. package/dist/utils/url.js.map +1 -0
  67. package/dist/utils/xml-parser.d.ts +13 -0
  68. package/dist/utils/xml-parser.d.ts.map +1 -0
  69. package/dist/utils/xml-parser.js +41 -0
  70. package/dist/utils/xml-parser.js.map +1 -0
  71. package/package.json +48 -0
package/README.md ADDED
@@ -0,0 +1,208 @@
1
+ # vercel-seo-audit
2
+
3
+ > **If you're using Vercel and Google hates your site, this is for you.**
4
+
5
+ A fast, developer-friendly CLI that explains **why Google isn’t indexing your Next.js site** — beyond the vague stuff in Search Console.
6
+ It detects the misconfigs that silently kill crawling and indexing: **redirect traps (308), missing robots/sitemap, noindex headers, canonical mismatches, and Vercel/Next.js quirks**.
7
+
8
+ ---
9
+
10
+ ## Why this exists
11
+
12
+ Google Search Console often reports symptoms like:
13
+ - *“Page with redirect”*
14
+ - *“Discovered – currently not indexed”*
15
+ - *“Alternate page with proper canonical”*
16
+
17
+ …but doesn’t tell you **what’s actually wrong** or **what to change**.
18
+
19
+ `vercel-seo-audit` turns those symptoms into **actionable fixes**.
20
+
21
+ ---
22
+
23
+ ## Quick start
24
+
25
+ ```bash
26
+ npx vercel-seo-audit https://yoursite.com
27
+ ````
28
+
29
+ Install globally (optional):
30
+
31
+ ```bash
32
+ npm i -g vercel-seo-audit
33
+ vercel-seo-audit https://yoursite.com
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Example output
39
+
40
+ ```txt
41
+ SEO Audit Report for https://yusufhan.dev/
42
+ Completed in 1118ms at 2026-01-31T12:30:54.448Z
43
+
44
+ Summary:
45
+ ⚠ 2 warnings
46
+ ℹ 1 info
47
+ ✔ 1 passed
48
+
49
+ ROBOTS
50
+ ────────────────────────────────────────
51
+ ⚠ [WARNING] robots.txt not found
52
+ → Create a robots.txt at /robots.txt
53
+
54
+ SITEMAP
55
+ ────────────────────────────────────────
56
+ ⚠ [WARNING] sitemap.xml not found
57
+ → Add app/sitemap.ts in Next.js App Router
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Usage
63
+
64
+ ```bash
65
+ # Basic audit
66
+ vercel-seo-audit https://yoursite.com
67
+
68
+ # JSON output (pipe to jq, save to file, feed to CI)
69
+ vercel-seo-audit https://yoursite.com --json
70
+
71
+ # Verbose mode — raw HTTP details for each finding
72
+ vercel-seo-audit https://yoursite.com --verbose
73
+
74
+ # Custom timeout (default: 10s)
75
+ vercel-seo-audit https://yoursite.com --timeout 15000
76
+ ```
77
+
78
+ ---
79
+
80
+ ## What it checks
81
+
82
+ ### Redirects
83
+
84
+ * Redirect chains & loops (homepage + common pages)
85
+ * HTTP → HTTPS redirect
86
+ * Trailing slash consistency (catches Next.js **308 traps**)
87
+ * Meta refresh redirects (`<meta http-equiv="refresh">`)
88
+ * Samples common routes: `/about`, `/contact`, `/blog`, `/pricing`
89
+
90
+ ### robots.txt
91
+
92
+ * Missing robots.txt
93
+ * `Disallow: /` (blocks everything)
94
+ * Googlebot-specific blocks
95
+ * Missing `Sitemap:` directive
96
+
97
+ ### Sitemap
98
+
99
+ * Missing or malformed `sitemap.xml`
100
+ * Sitemap redirects (some crawlers won’t follow)
101
+ * Empty sitemap / broken URLs (samples up to 10)
102
+ * Sitemap index support
103
+ * Cross-check with robots.txt `Sitemap:` directive
104
+
105
+ ### Metadata
106
+
107
+ * Canonical URL presence & mismatch
108
+ * `noindex` via meta tags **and** `X-Robots-Tag` header
109
+ * Missing `title`, `description`, `charset`, `viewport`
110
+ * Open Graph basics: `og:title`, `og:description`, `og:image`
111
+
112
+ ### Favicon
113
+
114
+ * Missing favicon entirely
115
+ * `/favicon.ico` exists but no `<link>` tag
116
+ * Conflicting favicon declarations (multiple icons)
117
+
118
+ ### Next.js / Vercel
119
+
120
+ * Detect Vercel deployment
121
+ * Detect Next.js trailing slash redirect behavior
122
+ * Middleware rewrite/redirect headers (best-effort)
123
+
124
+ ---
125
+
126
+ ## Severity & exit codes
127
+
128
+ Findings are categorized by severity:
129
+
130
+ | Icon | Severity | Meaning |
131
+ | ---- | ----------- | ---------------------------------- |
132
+ | `✖` | **error** | Actively hurting SEO — fix now |
133
+ | `⚠` | **warning** | Likely causing problems — fix soon |
134
+ | `ℹ` | **info** | Useful context |
135
+ | `✔` | **pass** | Looks good |
136
+
137
+ Exit codes:
138
+
139
+ | Code | Meaning |
140
+ | ---- | ------------------------------------------ |
141
+ | `0` | No errors found (warnings/info don’t fail) |
142
+ | `1` | One or more errors found |
143
+ | `2` | Crash / invalid input |
144
+
145
+ ---
146
+
147
+ ## CI / GitHub Actions
148
+
149
+ Fail the build only when **errors** exist:
150
+
151
+ ```yaml
152
+ name: SEO Audit
153
+ on:
154
+ workflow_dispatch:
155
+ push:
156
+ branches: [main]
157
+
158
+ jobs:
159
+ audit:
160
+ runs-on: ubuntu-latest
161
+ steps:
162
+ - uses: actions/checkout@v4
163
+ - uses: actions/setup-node@v4
164
+ with:
165
+ node-version: 20
166
+ - run: npx vercel-seo-audit https://yoursite.com --json
167
+ ```
168
+
169
+ Tip: If you want warnings to fail CI too, add a `--strict` flag in roadmap (see below).
170
+
171
+ ---
172
+
173
+ ## Roadmap
174
+
175
+ * [ ] `--strict` (warnings fail with exit code 1)
176
+ * [ ] `--pages` to customize sampled paths (`/about,/pricing`)
177
+ * [ ] `--user-agent` presets (`googlebot`, `bingbot`)
178
+ * [ ] `--report` to write `report.json` / `report.md`
179
+ * [ ] GitHub Action marketplace wrapper
180
+
181
+ ---
182
+
183
+ ## FAQ
184
+
185
+ **Does this replace Google Search Console?**
186
+ No — it explains & verifies the things Search Console often reports vaguely.
187
+
188
+ **Will it scan my entire site?**
189
+ No. It checks critical endpoints + samples common pages to stay fast.
190
+
191
+ **Does it work on non-Next.js sites?**
192
+ Yes for most checks (redirects/robots/sitemap/metadata). Some checks are Next.js/Vercel-specific.
193
+
194
+ ---
195
+
196
+ ## Contributing
197
+
198
+ PRs welcome. If you’re fixing a false positive, include:
199
+
200
+ * the URL (or a reproducible HTML sample)
201
+ * expected behavior
202
+ * actual output
203
+
204
+ ---
205
+
206
+ ## License
207
+
208
+ MIT
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import('../dist/cli.js');
@@ -0,0 +1,3 @@
1
+ import type { AuditContext, AuditFinding } from '../types.js';
2
+ export declare function auditFavicon(ctx: AuditContext): Promise<AuditFinding[]>;
3
+ //# sourceMappingURL=favicon.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"favicon.d.ts","sourceRoot":"","sources":["../../src/audit/favicon.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAM9D,wBAAsB,YAAY,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CA6E7E"}
@@ -0,0 +1,73 @@
1
+ import { DEFAULT_PATHS } from '../constants.js';
2
+ import { fetchHead, fetchPage } from '../utils/http.js';
3
+ import { getFaviconLinks } from '../utils/html-parser.js';
4
+ import { getOrigin } from '../utils/url.js';
5
+ export async function auditFavicon(ctx) {
6
+ const findings = [];
7
+ const origin = getOrigin(ctx.normalizedUrl);
8
+ const faviconUrl = `${origin}${DEFAULT_PATHS.faviconIco}`;
9
+ // 1. Check /favicon.ico directly
10
+ let faviconIcoExists = false;
11
+ try {
12
+ const { status } = await fetchHead(faviconUrl, ctx.fetchOptions);
13
+ faviconIcoExists = status === 200;
14
+ }
15
+ catch {
16
+ // Ignore fetch errors
17
+ }
18
+ // 2. Check HTML link tags for favicons
19
+ let html = ctx.html;
20
+ if (!html) {
21
+ try {
22
+ const page = await fetchPage(ctx.normalizedUrl, ctx.fetchOptions);
23
+ html = page.body;
24
+ }
25
+ catch {
26
+ return findings;
27
+ }
28
+ }
29
+ const htmlFavicons = getFaviconLinks(html);
30
+ const hasHtmlFavicon = htmlFavicons.length > 0;
31
+ // 3. Report findings
32
+ if (!faviconIcoExists && !hasHtmlFavicon) {
33
+ findings.push({
34
+ code: 'FAVICON_MISSING',
35
+ severity: 'warning',
36
+ category: 'favicon',
37
+ message: 'No favicon found',
38
+ explanation: 'A missing favicon causes 404 errors in server logs and looks unprofessional in browser tabs and bookmarks.',
39
+ suggestion: 'Add a favicon.ico at the root of your site or declare one via <link rel="icon"> in your HTML.',
40
+ url: faviconUrl,
41
+ });
42
+ }
43
+ else if (!hasHtmlFavicon && faviconIcoExists) {
44
+ findings.push({
45
+ code: 'FAVICON_HTML_MISSING',
46
+ severity: 'info',
47
+ category: 'favicon',
48
+ message: 'Favicon exists at /favicon.ico but no HTML link tag declares it',
49
+ explanation: 'While browsers will find /favicon.ico by convention, explicitly declaring it in HTML ensures compatibility and allows specifying multiple sizes.',
50
+ suggestion: 'Add <link rel="icon" href="/favicon.ico"> to your HTML <head>.',
51
+ url: faviconUrl,
52
+ });
53
+ }
54
+ else if (hasHtmlFavicon && faviconIcoExists) {
55
+ // Check for potential conflicts
56
+ const icoLinks = htmlFavicons.filter((f) => f.href.endsWith('.ico') || f.href.includes('favicon.ico'));
57
+ const nonIcoLinks = htmlFavicons.filter((f) => !f.href.endsWith('.ico') && !f.href.includes('favicon.ico'));
58
+ if (icoLinks.length > 0 && nonIcoLinks.length > 0) {
59
+ findings.push({
60
+ code: 'FAVICON_CONFLICT',
61
+ severity: 'info',
62
+ category: 'favicon',
63
+ message: `Multiple favicon formats declared (${htmlFavicons.length} links)`,
64
+ explanation: 'Multiple favicon declarations are normal for supporting different devices, but verify they all resolve.',
65
+ suggestion: 'Ensure all declared favicon URLs are accessible.',
66
+ details: { favicons: htmlFavicons },
67
+ url: ctx.normalizedUrl,
68
+ });
69
+ }
70
+ }
71
+ return findings;
72
+ }
73
+ //# sourceMappingURL=favicon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"favicon.js","sourceRoot":"","sources":["../../src/audit/favicon.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export { auditRedirects } from './redirects.js';
2
+ export { auditRobots } from './robots.js';
3
+ export { auditSitemap } from './sitemap.js';
4
+ export { auditMetadata } from './metadata.js';
5
+ export { auditFavicon } from './favicon.js';
6
+ export { auditNextjs } from './nextjs.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/audit/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { auditRedirects } from './redirects.js';
2
+ export { auditRobots } from './robots.js';
3
+ export { auditSitemap } from './sitemap.js';
4
+ export { auditMetadata } from './metadata.js';
5
+ export { auditFavicon } from './favicon.js';
6
+ export { auditNextjs } from './nextjs.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/audit/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { AuditContext, AuditFinding } from '../types.js';
2
+ export declare function auditMetadata(ctx: AuditContext): Promise<AuditFinding[]>;
3
+ //# sourceMappingURL=metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../../src/audit/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAY9D,wBAAsB,aAAa,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAgN9E"}
@@ -0,0 +1,183 @@
1
+ import { fetchPage } from '../utils/http.js';
2
+ import { getCanonicalUrl, getNoindexDirective, getCharset, getViewport, getTitle, getMetaTag, } from '../utils/html-parser.js';
3
+ import { isSameOrigin } from '../utils/url.js';
4
+ export async function auditMetadata(ctx) {
5
+ const findings = [];
6
+ const { normalizedUrl, fetchOptions } = ctx;
7
+ let html;
8
+ let headers;
9
+ let finalUrl;
10
+ try {
11
+ const page = await fetchPage(normalizedUrl, fetchOptions);
12
+ html = page.body;
13
+ headers = page.headers;
14
+ finalUrl = page.finalUrl;
15
+ ctx.html = html;
16
+ ctx.headers = Object.fromEntries(headers.entries());
17
+ }
18
+ catch {
19
+ return findings;
20
+ }
21
+ // 1. Noindex check — meta tag
22
+ if (getNoindexDirective(html)) {
23
+ findings.push({
24
+ code: 'NOINDEX_DETECTED',
25
+ severity: 'error',
26
+ category: 'indexing',
27
+ message: 'noindex meta tag detected on homepage',
28
+ explanation: 'A noindex directive tells search engines not to include this page in search results.',
29
+ suggestion: 'Remove the noindex directive unless you intentionally want to prevent indexing.',
30
+ url: normalizedUrl,
31
+ });
32
+ }
33
+ // 2. Noindex check — X-Robots-Tag header
34
+ const xRobots = headers.get('x-robots-tag') ?? '';
35
+ if (xRobots.toLowerCase().includes('noindex')) {
36
+ findings.push({
37
+ code: 'X_ROBOTS_NOINDEX',
38
+ severity: 'error',
39
+ category: 'indexing',
40
+ message: 'X-Robots-Tag: noindex header detected',
41
+ explanation: 'The X-Robots-Tag header with noindex prevents search engines from indexing this page.',
42
+ suggestion: 'Remove the X-Robots-Tag noindex header from your server configuration or middleware.',
43
+ details: { header: xRobots },
44
+ url: normalizedUrl,
45
+ });
46
+ }
47
+ // 3. Canonical URL checks
48
+ const canonical = getCanonicalUrl(html);
49
+ if (!canonical) {
50
+ findings.push({
51
+ code: 'CANONICAL_MISSING',
52
+ severity: 'warning',
53
+ category: 'metadata',
54
+ message: 'No canonical URL found on homepage',
55
+ explanation: 'Without a canonical tag, search engines may index duplicate versions of your page.',
56
+ suggestion: 'Add a <link rel="canonical"> tag pointing to the preferred URL of this page.',
57
+ url: normalizedUrl,
58
+ });
59
+ }
60
+ else {
61
+ try {
62
+ const canonicalFull = new URL(canonical, finalUrl).href;
63
+ if (canonicalFull !== finalUrl && canonicalFull !== normalizedUrl) {
64
+ findings.push({
65
+ code: 'CANONICAL_MISMATCH',
66
+ severity: 'warning',
67
+ category: 'metadata',
68
+ message: 'Canonical URL does not match the page URL',
69
+ explanation: 'A mismatched canonical signals to search engines that this page is a duplicate of another.',
70
+ suggestion: 'Ensure the canonical URL matches the page URL, or verify the mismatch is intentional.',
71
+ details: { canonical: canonicalFull, pageUrl: finalUrl },
72
+ url: normalizedUrl,
73
+ });
74
+ }
75
+ if (!isSameOrigin(canonicalFull, normalizedUrl)) {
76
+ findings.push({
77
+ code: 'CANONICAL_EXTERNAL',
78
+ severity: 'info',
79
+ category: 'metadata',
80
+ message: 'Canonical URL points to a different domain',
81
+ explanation: 'An external canonical tells search engines that this content originates on another domain.',
82
+ suggestion: 'Verify this is intentional. External canonicals transfer ranking signals to the other domain.',
83
+ details: { canonical: canonicalFull },
84
+ url: normalizedUrl,
85
+ });
86
+ }
87
+ }
88
+ catch {
89
+ // Invalid canonical URL
90
+ }
91
+ }
92
+ // 4. Charset
93
+ const charset = getCharset(html);
94
+ if (!charset) {
95
+ findings.push({
96
+ code: 'CHARSET_MISSING',
97
+ severity: 'info',
98
+ category: 'metadata',
99
+ message: 'No charset declaration found',
100
+ explanation: 'Without a charset declaration, browsers may misinterpret special characters on your page.',
101
+ suggestion: 'Add <meta charset="utf-8"> to the <head> of your document.',
102
+ url: normalizedUrl,
103
+ });
104
+ }
105
+ // 5. Viewport
106
+ const viewport = getViewport(html);
107
+ if (!viewport) {
108
+ findings.push({
109
+ code: 'VIEWPORT_MISSING',
110
+ severity: 'warning',
111
+ category: 'metadata',
112
+ message: 'No viewport meta tag found',
113
+ explanation: 'Without a viewport tag, mobile devices may render the page at desktop width, harming mobile SEO.',
114
+ suggestion: 'Add <meta name="viewport" content="width=device-width, initial-scale=1">.',
115
+ url: normalizedUrl,
116
+ });
117
+ }
118
+ // 6. Title
119
+ const title = getTitle(html);
120
+ if (!title) {
121
+ findings.push({
122
+ code: 'TITLE_MISSING',
123
+ severity: 'warning',
124
+ category: 'metadata',
125
+ message: 'No <title> tag found',
126
+ explanation: 'The title tag is one of the most important on-page SEO elements. Missing titles hurt rankings.',
127
+ suggestion: 'Add a unique, descriptive <title> tag to your page.',
128
+ url: normalizedUrl,
129
+ });
130
+ }
131
+ // 7. Meta description
132
+ const description = getMetaTag(html, 'description');
133
+ if (!description) {
134
+ findings.push({
135
+ code: 'DESCRIPTION_MISSING',
136
+ severity: 'info',
137
+ category: 'metadata',
138
+ message: 'No meta description found',
139
+ explanation: 'Meta descriptions appear in search results and can improve click-through rates.',
140
+ suggestion: 'Add a <meta name="description"> tag with a compelling summary of your page.',
141
+ url: normalizedUrl,
142
+ });
143
+ }
144
+ // 8. Open Graph tags
145
+ const ogTitle = getMetaTag(html, 'og:title');
146
+ const ogDescription = getMetaTag(html, 'og:description');
147
+ const ogImage = getMetaTag(html, 'og:image');
148
+ if (!ogTitle) {
149
+ findings.push({
150
+ code: 'OG_TITLE_MISSING',
151
+ severity: 'info',
152
+ category: 'metadata',
153
+ message: 'No og:title meta tag found',
154
+ explanation: 'Open Graph tags control how your page appears when shared on social media platforms.',
155
+ suggestion: 'Add <meta property="og:title"> for better social sharing previews.',
156
+ url: normalizedUrl,
157
+ });
158
+ }
159
+ if (!ogDescription) {
160
+ findings.push({
161
+ code: 'OG_DESCRIPTION_MISSING',
162
+ severity: 'info',
163
+ category: 'metadata',
164
+ message: 'No og:description meta tag found',
165
+ explanation: 'og:description controls the description shown in social media previews.',
166
+ suggestion: 'Add <meta property="og:description"> for better social sharing.',
167
+ url: normalizedUrl,
168
+ });
169
+ }
170
+ if (!ogImage) {
171
+ findings.push({
172
+ code: 'OG_IMAGE_MISSING',
173
+ severity: 'info',
174
+ category: 'metadata',
175
+ message: 'No og:image meta tag found',
176
+ explanation: 'Social media platforms display a default placeholder when no og:image is set.',
177
+ suggestion: 'Add <meta property="og:image"> with a representative image URL.',
178
+ url: normalizedUrl,
179
+ });
180
+ }
181
+ return findings;
182
+ }
183
+ //# sourceMappingURL=metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata.js","sourceRoot":"","sources":["../../src/audit/metadata.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { AuditContext, AuditFinding } from '../types.js';
2
+ export declare function auditNextjs(ctx: AuditContext): Promise<AuditFinding[]>;
3
+ //# sourceMappingURL=nextjs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextjs.d.ts","sourceRoot":"","sources":["../../src/audit/nextjs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAI9D,wBAAsB,WAAW,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAmH5E"}
@@ -0,0 +1,104 @@
1
+ import { fetchPage, fetchWithoutRedirect } from '../utils/http.js';
2
+ import { addTrailingSlash, removeTrailingSlash, hasTrailingSlash } from '../utils/url.js';
3
+ export async function auditNextjs(ctx) {
4
+ const findings = [];
5
+ const { normalizedUrl, fetchOptions } = ctx;
6
+ // 1. Fetch the page to get headers
7
+ let headers;
8
+ try {
9
+ if (ctx.headers) {
10
+ headers = ctx.headers;
11
+ }
12
+ else {
13
+ const page = await fetchPage(normalizedUrl, fetchOptions);
14
+ headers = Object.fromEntries(page.headers.entries());
15
+ }
16
+ }
17
+ catch {
18
+ return findings;
19
+ }
20
+ // 2. Vercel detection
21
+ const server = headers['server'] ?? '';
22
+ const xVercelId = headers['x-vercel-id'] ?? '';
23
+ const xPoweredBy = headers['x-powered-by'] ?? '';
24
+ const isVercel = server.toLowerCase().includes('vercel') || !!xVercelId;
25
+ const isNextjs = xPoweredBy.toLowerCase().includes('next.js');
26
+ if (isVercel) {
27
+ findings.push({
28
+ code: 'VERCEL_DETECTED',
29
+ severity: 'info',
30
+ category: 'nextjs',
31
+ message: `Vercel deployment detected${isNextjs ? ' (Next.js)' : ''}`,
32
+ explanation: 'Vercel-specific optimizations and checks are being applied to this audit.',
33
+ suggestion: 'No action needed. Vercel-specific checks are enabled.',
34
+ details: { server, xVercelId, xPoweredBy },
35
+ url: normalizedUrl,
36
+ });
37
+ }
38
+ // 3. Next.js trailing slash 308 behavior
39
+ try {
40
+ const testUrl = hasTrailingSlash(normalizedUrl)
41
+ ? removeTrailingSlash(normalizedUrl)
42
+ : addTrailingSlash(normalizedUrl);
43
+ const res = await fetchWithoutRedirect(testUrl, fetchOptions);
44
+ if (res.status === 308) {
45
+ findings.push({
46
+ code: 'NEXTJS_TRAILING_SLASH_308',
47
+ severity: 'info',
48
+ category: 'nextjs',
49
+ message: 'Next.js 308 permanent redirect for trailing slash normalization',
50
+ explanation: 'Next.js uses 308 (Permanent Redirect) to enforce its trailingSlash configuration. This is normal behavior but ensure it matches your intended URL structure.',
51
+ suggestion: 'If this is unexpected, check next.config.js trailingSlash setting. 308 redirects are cached by browsers.',
52
+ details: {
53
+ testedUrl: testUrl,
54
+ status: 308,
55
+ location: res.headers.get('location'),
56
+ },
57
+ url: testUrl,
58
+ });
59
+ }
60
+ }
61
+ catch {
62
+ // Not critical
63
+ }
64
+ // 4. Middleware redirect headers
65
+ const xMiddlewareRewrite = headers['x-middleware-rewrite'] ?? '';
66
+ const xMiddlewareRedirect = headers['x-middleware-redirect'] ?? '';
67
+ const xMiddlewareNext = headers['x-middleware-next'] ?? '';
68
+ if (xMiddlewareRewrite || xMiddlewareRedirect) {
69
+ findings.push({
70
+ code: 'MIDDLEWARE_REDIRECT',
71
+ severity: 'info',
72
+ category: 'nextjs',
73
+ message: 'Next.js middleware is modifying the request',
74
+ explanation: 'Middleware rewrites or redirects can affect how search engines see your pages. Ensure middleware is not unintentionally altering SEO-critical pages.',
75
+ suggestion: 'Review your middleware.ts to ensure it does not redirect or rewrite SEO-critical URLs.',
76
+ details: {
77
+ rewrite: xMiddlewareRewrite || undefined,
78
+ redirect: xMiddlewareRedirect || undefined,
79
+ next: xMiddlewareNext || undefined,
80
+ },
81
+ url: normalizedUrl,
82
+ });
83
+ }
84
+ // 5. App Router metadata check (presence of Next.js metadata headers)
85
+ const html = ctx.html;
86
+ if (html) {
87
+ const hasNextMetadata = html.includes('next-head-count') ||
88
+ html.includes('__next') ||
89
+ html.includes('_next/static');
90
+ if (isNextjs && !hasNextMetadata) {
91
+ findings.push({
92
+ code: 'APP_ROUTER_METADATA',
93
+ severity: 'info',
94
+ category: 'nextjs',
95
+ message: 'Next.js detected but standard Next.js markers not found in HTML',
96
+ explanation: 'This may indicate a custom rendering setup or edge runtime that could affect metadata generation.',
97
+ suggestion: 'Ensure your Next.js App Router pages export proper metadata using the Metadata API.',
98
+ url: normalizedUrl,
99
+ });
100
+ }
101
+ }
102
+ return findings;
103
+ }
104
+ //# sourceMappingURL=nextjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextjs.js","sourceRoot":"","sources":["../../src/audit/nextjs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { AuditContext, AuditFinding } from '../types.js';
2
+ export declare function auditRedirects(ctx: AuditContext): Promise<AuditFinding[]>;
3
+ //# sourceMappingURL=redirects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redirects.d.ts","sourceRoot":"","sources":["../../src/audit/redirects.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAiB9D,wBAAsB,cAAc,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CA8I/E"}