Skip to content

Commit 064dbbc

Browse files
temp(docs): add temp docs seo audit markdown file
1 parent 67daddc commit 064dbbc

1 file changed

Lines changed: 324 additions & 0 deletions

File tree

apps/docs/seo-audit.md

Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
# SEO Audit: Prisma Docs (`apps/docs`)
2+
3+
**Date:** 2026-04-06
4+
**Site:** `https://www.prisma.io/docs` (Next.js 16 App Router + Fumadocs)
5+
**Pages audited:** 363 v7 MDX + 316 v6 MDX = 679 total
6+
7+
---
8+
9+
## SEO Health Score: 72 / 100
10+
11+
| Category | Score | Weight | Weighted |
12+
|---|---|---|---|
13+
| Technical SEO | 65/100 | 22% | 14.3 |
14+
| Content Quality | 78/100 | 23% | 17.9 |
15+
| On-Page SEO | 82/100 | 20% | 16.4 |
16+
| Schema / Structured Data | 60/100 | 10% | 6.0 |
17+
| Performance (CWV) | 45/100 | 10% | 4.5 |
18+
| AI Search Readiness | 85/100 | 10% | 8.5 |
19+
| Images | 50/100 | 5% | 2.5 |
20+
| **Total** | | | **72** |
21+
22+
---
23+
24+
## Critical Issues
25+
26+
### 1. Image optimization disabled (`images: { unoptimized: true }`)
27+
28+
**File:** `next.config.mjs:233`
29+
**Priority:** Critical
30+
31+
`images: { unoptimized: true }` disables Next.js's built-in image optimization (WebP conversion, lazy loading, responsive srcsets, automatic sizing). This directly harms **Largest Contentful Paint (LCP)** — a Core Web Vitals ranking signal. Any `<Image>` components in the docs render as unoptimized `<img>` tags.
32+
33+
**Fix:** Remove `unoptimized: true`. If this was set for a static export reason, use the Vercel Image Optimization service (included on Vercel deployments) or configure `loader: 'custom'` with a CDN.
34+
35+
---
36+
37+
### 2. Root redirect is a 302 (temporary), not 301 (permanent)
38+
39+
**File:** `next.config.mjs:213-219`
40+
**Priority:** Critical
41+
42+
```js
43+
{
44+
source: "/",
45+
destination: "/docs",
46+
permanent: false, // ← 302, should be 301
47+
basePath: false,
48+
}
49+
```
50+
51+
A 302 does not pass PageRank/link equity to `/docs`. Any external links to `https://www.prisma.io/` (e.g., homepage links from social profiles, blog posts, partner sites) lose their link value. This has been a long-standing misconfiguration that compounds over time as backlinks accumulate.
52+
53+
**Fix:** Change `permanent: false``permanent: true`.
54+
55+
---
56+
57+
## High Priority
58+
59+
### 3. 55 pages missing `description` frontmatter
60+
61+
**File:** `content/docs/management-api/endpoints/**`
62+
**Priority:** High
63+
64+
~15% of v7 content has no meta description. Without a `description:` field, `generateMetadata()` returns `undefined` and Google auto-generates the snippet — usually worse for CTR. Affected pages are almost entirely auto-generated Management API endpoint pages.
65+
66+
**Fix:** Add `description:` frontmatter to all 55 pages, or add a fallback in `generateMetadata()` using the first paragraph of MDX content.
67+
68+
---
69+
70+
### 4. TechArticle schema missing `datePublished`
71+
72+
**File:** `src/components/structured-data.tsx:13-38`
73+
**Priority:** High
74+
75+
The TechArticle JSON-LD includes `dateModified` (when available) but never `datePublished`. Google's TechArticle rich result spec requires `datePublished` for eligibility.
76+
77+
```ts
78+
const schema = {
79+
'@type': 'TechArticle',
80+
// ❌ datePublished: missing
81+
dateModified: lastModified?.toISOString(),
82+
```
83+
84+
**Fix:** Add `datePublished` to the frontmatter schema and populate it. If unavailable, use `dateModified` as a fallback.
85+
86+
---
87+
88+
### 5. BreadcrumbList names derived from slugs, not page titles
89+
90+
**File:** `src/components/structured-data.tsx:62-68`
91+
**Priority:** High
92+
93+
```ts
94+
name: slug.charAt(0).toUpperCase() + slug.slice(1).replace(/-/g, ' '),
95+
```
96+
97+
This produces names like `"Orm"`, `"Prisma client"`, `"Setup and configuration"` instead of proper display names ("Prisma ORM", "Prisma Client", "Setup and Configuration"). Google shows BreadcrumbList names in search results.
98+
99+
**Fix:** Use the page's `data.title` or the parent section's `meta.json` title for intermediate breadcrumb items, not slug-derived strings.
100+
101+
---
102+
103+
### 6. No `WebSite` schema with `SearchAction`
104+
105+
**File:** `src/app/layout.tsx` (missing)
106+
**Priority:** High
107+
108+
The site has fully functional search (Orama-based at `/api/search`). Adding a `WebSite` schema with `SearchAction` at root level enables Google's **Sitelinks Searchbox** for branded searches ("prisma docs").
109+
110+
**Fix:** Add to `src/app/layout.tsx`:
111+
112+
```json
113+
{
114+
"@context": "https://schema.org",
115+
"@type": "WebSite",
116+
"name": "Prisma Documentation",
117+
"url": "https://www.prisma.io/docs",
118+
"potentialAction": {
119+
"@type": "SearchAction",
120+
"target": {
121+
"@type": "EntryPoint",
122+
"urlTemplate": "https://www.prisma.io/docs?q={search_term_string}"
123+
},
124+
"query-input": "required name=search_term_string"
125+
}
126+
}
127+
```
128+
129+
---
130+
131+
### 7. Flat sitemap priorities — all pages equal
132+
133+
**File:** `src/app/(docs)/sitemap.ts:12-21`
134+
**Priority:** High
135+
136+
Every v7 page gets `priority: 0.5`. The sitemap spec recommends differentiated priorities: root/index pages `1.0`, section landing pages `0.8`, leaf pages `0.5-0.6`. With 679 URLs, crawl budget matters — especially for v6 legacy pages.
137+
138+
**Fix:**
139+
140+
```ts
141+
priority: page.slugs.length === 0 ? 1.0 // root
142+
: page.slugs.length === 1 ? 0.8 // section
143+
: 0.5, // leaf
144+
```
145+
146+
---
147+
148+
### 8. Soft-redirect stub pages indexed
149+
150+
**Files:** `content/docs/accelerate/connection-pooling.mdx`, `content/docs/accelerate/caching.mdx`
151+
**Priority:** High
152+
153+
These 9-line stub pages contain only "This page has moved..." text and are included in the sitemap with priority 0.5. They pass no canonical signal to the new URL and will be indexed as thin content.
154+
155+
**Fix:** Add `noindex: true` frontmatter support in `generateMetadata()`, or replace the stubs with proper HTTP 301 redirects in `vercel.json`.
156+
157+
---
158+
159+
## Medium Priority
160+
161+
### 9. Missing HSTS header
162+
163+
**File:** `next.config.mjs:179-200`
164+
**Priority:** Medium
165+
166+
Security headers include CSP, `X-Frame-Options`, `X-Content-Type-Options`, and `Referrer-Policy` — but no `Strict-Transport-Security`. HSTS is a ranking signal for HTTPS enforcement.
167+
168+
**Fix:**
169+
170+
```js
171+
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" }
172+
```
173+
174+
---
175+
176+
### 10. No `Organization` schema at root level
177+
178+
**File:** `src/app/layout.tsx` (missing)
179+
**Priority:** Medium
180+
181+
No site-level Organization schema with `sameAs` social profiles, logo, or contact info. This enriches Google's Knowledge Panel for branded searches.
182+
183+
**Fix:** Add to `src/app/layout.tsx`:
184+
185+
```json
186+
{
187+
"@context": "https://schema.org",
188+
"@type": "Organization",
189+
"name": "Prisma",
190+
"url": "https://www.prisma.io",
191+
"logo": "https://www.prisma.io/images/logo.svg",
192+
"sameAs": [
193+
"https://twitter.com/prisma",
194+
"https://github.com/prisma/prisma",
195+
"https://www.linkedin.com/company/prismaio"
196+
]
197+
}
198+
```
199+
200+
---
201+
202+
### 11. `llms.txt` includes v6 and v7 without version guidance
203+
204+
**File:** `src/app/llms.txt/route.ts:8-40`
205+
**Priority:** Medium
206+
207+
The llms.txt lists both v7 ("Latest") and v6 pages. AI models crawling this see duplicate concepts across versions with no signal about which is authoritative, diluting AI citation quality.
208+
209+
**Fix:** Add a preamble section:
210+
211+
```markdown
212+
> This documentation covers Prisma v7 (current) and v6 (legacy).
213+
> Prefer "Latest" section pages for current recommendations.
214+
> v6 pages are maintained for backwards compatibility only.
215+
```
216+
217+
---
218+
219+
### 12. Sitemap `revalidate = false` — never auto-refreshes
220+
221+
**File:** `src/app/(docs)/sitemap.ts:5`
222+
**Priority:** Medium
223+
224+
`export const revalidate = false` means the sitemap is generated once at build time and never revalidated via ISR. New pages added between deployments won't appear in the sitemap until the next full deploy.
225+
226+
**Fix:** Set `export const revalidate = 3600` (or another appropriate interval).
227+
228+
---
229+
230+
### 13. OG image route fetches Google Fonts on every cold start
231+
232+
**File:** `src/app/og/[...slug]/route.tsx`
233+
**Priority:** Medium
234+
235+
The OG image route fetches 3 Google Fonts (Barlow, Inter, JetBrains Mono) via `loadGoogleFont()`. The module-level `fontCache` helps within a runtime instance but doesn't persist across cold starts — each cold start adds font-fetch latency to OG image generation.
236+
237+
**Fix:** Bundle the font files as static assets in `/public/fonts/` and load them from the filesystem instead of fetching from Google.
238+
239+
---
240+
241+
### 14. v6 canonical URLs may signal duplicate content
242+
243+
**File:** `src/app/(docs)/v6/[[...slug]]/page.tsx:104`
244+
**Priority:** Medium
245+
246+
v6 pages set their canonical to `/docs/v6/orm/...` — they do **not** point to the equivalent v7 page. For pages where v6 content is nearly identical to v7, this creates genuine duplicate content without a disambiguation signal.
247+
248+
**Fix:** For v6 pages with a nearly-identical v7 equivalent, set the canonical to the v7 URL. Or add `noindex` to low-value v6 fallback pages.
249+
250+
---
251+
252+
## Low Priority
253+
254+
### 15. Missing `Permissions-Policy` header
255+
256+
**File:** `next.config.mjs:179-200`
257+
**Priority:** Low
258+
259+
**Fix:** Add: `{ key: "Permissions-Policy", value: "camera=(), microphone=(), geolocation=()" }`
260+
261+
---
262+
263+
### 16. No image sitemap
264+
265+
**Priority:** Low
266+
267+
The docs site generates OG images for every page. Including them in an image sitemap could improve image indexation and image search discovery.
268+
269+
---
270+
271+
### 17. `assetPrefix` diverges from `basePath`
272+
273+
**File:** `next.config.mjs:229-230`
274+
**Priority:** Low
275+
276+
`assetPrefix: "/docs-static"` while pages are at `basePath: "/docs"`. Appears intentional (CDN routing), but worth verifying the CDN mapping is correct in production to prevent JS/CSS 404s.
277+
278+
---
279+
280+
### 18. No `.well-known/security.txt`
281+
282+
**Priority:** Low
283+
284+
Minor domain authority/trust signal. Easy win for security hygiene.
285+
286+
---
287+
288+
## What's Working Well
289+
290+
- **Meta descriptions:** 100% of pages have custom `metaDescription`
291+
- **Dynamic OG images:** Fully branded, covers all 679 pages with section-aware styling
292+
- **TechArticle + BreadcrumbList schema:** On every page (data quality fixes needed)
293+
- **llms.txt + llms-full.txt:** Ahead of most documentation sites for AI readiness
294+
- **robots.txt:** Correctly disallows `/api/`, `/_next/`, `/og/`, query params
295+
- **Canonical URLs:** Properly implemented via `generateMetadata()` on all pages
296+
- **Twitter card + OG metadata:** Full coverage on all pages
297+
- **PostHog analytics:** 404 tracking enables proactive broken link detection
298+
- **CSP headers:** Comprehensive Content Security Policy
299+
- **ISR-compatible:** `generateStaticParams()` + dynamic page routes
300+
301+
---
302+
303+
## Prioritized Action Plan
304+
305+
| Priority | Issue | Effort | Impact |
306+
|---|---|---|---|
307+
| **Critical** | Image optimization disabled (`images: unoptimized`) | Low | LCP, CWV |
308+
| **Critical** | 302 → 301 root redirect | Trivial | Link equity |
309+
| **High** | Add `datePublished` to TechArticle schema | Low | Rich results |
310+
| **High** | Fix BreadcrumbList names (slugs → titles) | Medium | SERP display |
311+
| **High** | Add WebSite + SearchAction schema | Low | Sitelinks Searchbox |
312+
| **High** | Fix stub/moved pages (noindex or redirect) | Low | Thin content |
313+
| **High** | Add descriptions to 55 endpoint pages | Medium | CTR |
314+
| **High** | Differentiate sitemap priorities by depth | Low | Crawl budget |
315+
| **Medium** | Add HSTS header | Trivial | Security signal |
316+
| **Medium** | Add Organization schema at root | Low | Knowledge Panel |
317+
| **Medium** | Improve llms.txt preamble | Trivial | AI citation quality |
318+
| **Medium** | Set sitemap `revalidate` to a TTL | Trivial | Sitemap freshness |
319+
| **Medium** | Bundle OG fonts locally | Medium | OG render speed |
320+
| **Medium** | v6 canonical strategy | Medium | Duplicate content |
321+
| **Low** | Permissions-Policy header | Trivial | Security hygiene |
322+
| **Low** | Image sitemap | Low | Image search |
323+
| **Low** | Verify assetPrefix CDN mapping | Low | Asset delivery |
324+
| **Low** | Add `.well-known/security.txt` | Trivial | Trust signal |

0 commit comments

Comments
 (0)