Public Content Seo Audit 2026 06 17
Public content SEO audit — marketing, docs, blog, academy, design-system, ui-library
Date: 2026-06-17
Scope reviewed: apps/www/app, apps/blog, apps/docs, apps/academy, apps/design-system, and apps/ui-library, focused on page metadata, heading structure, indexable content, internal links, image alt usage where practical, and duplicate/thin metadata patterns.
Ranked findings#
P0 — No critical blocker found#
The six public surfaces all define root metadata in their app layouts, and the highest-value public landing pages have crawlable text, internal links, and explicit titles/descriptions. I did not find a single site-wide robots/noindex misconfiguration in the static app-layout review.
Evidence:
apps/www/app/layout.tsxdefines root metadata and a skip link to#main-content.apps/blog/app/layout.tsxdefinesBLOG_NAMEtitle templating andBLOG_DESCRIPTION.apps/docs/app/layout.tsxsetsmetadataBase, production-awarerobots, Open Graph, and Twitter metadata.apps/academy/app/layout.tsxsets templated titles, canonical/academy, Open Graph image alt text, and Twitter metadata.apps/design-system/app/layout.tsxandapps/ui-library/app/layout.tsxdefine app-level title, description, Open Graph, and Twitter metadata.
P1 — Many public app-router pages rely on inherited metadata or route-specific generators are absent#
Several public pages do not export route-level metadata/generateMetadata. This is not automatically wrong, but it means search snippets can collapse to generic app-level descriptions, especially for high-intent routes.
Representative files flagged by the static page scan:
apps/www/app/contact/page.tsxandapps/www/app/contact-sales/page.tsx— contact intent pages with no route-level metadata.apps/www/app/get-quote/page.tsxandapps/www/app/get-quote/[category]/page.tsx— conversion pages with no route-level metadata.apps/www/app/guides/[[...slug]]/page.tsx— guide pages have no local metadata export in the page module.apps/www/app/trainings/[platform]/page.tsxandapps/www/app/trainings/[platform]/[slug]/page.tsx— training detail/category routes have no route-level metadata export in the page module.apps/academy/app/(course)/courses/page.tsx,apps/academy/app/(course)/courses/[category]/page.tsx,apps/academy/app/(trainings)/paths/page.tsx, andapps/academy/app/(trainings)/paths/[slug]/page.tsx— course/path discovery routes lack route-level metadata.apps/design-system/app/(app)/page.tsxandapps/ui-library/app/(app)/page.tsx— app home pages inherit layout metadata only.
Recommendation: add deterministic route-level metadata/generateMetadata for conversion and index pages first, using each app's existing metadata helper patterns (apps/www/lib/metadata.ts, apps/blog/lib/seo.ts, academy buildSeoMetadata usage in apps/academy/app/(trainings)/page.tsx).
P1 — Several page modules have no literal <h1> in the page component#
The static scan found many page modules with no literal <h1> tag. Some may render headings through imported components or MDX, so this requires rendered-page validation before changing code. However, the risk is that route-level content starts with cards/navigation instead of one clear document heading.
Representative files:
apps/www/app/page.tsxapps/www/app/about/page.tsxapps/www/app/pricing/page.tsxapps/www/app/case-studies/page.tsxapps/www/app/(pages)/features/page.tsxapps/blog/app/page.tsxapps/blog/app/[slug]/page.tsxapps/docs/app/guides/services/[[...slug]]/page.tsxapps/academy/app/(course)/courses/page.tsx
Counterexamples with clear literal page headings include apps/docs/app/page.tsx, apps/academy/app/(trainings)/page.tsx, apps/design-system/app/(app)/page.tsx, and apps/ui-library/app/(app)/page.tsx.
Recommendation: during the next SEO pass, validate rendered HTML for top landing pages and ensure each indexable route has exactly one meaningful <h1> matching the search intent and title.
P2 — Academy content has missing and duplicate frontmatter descriptions at scale#
A static frontmatter scan across apps/academy/content, apps/blog/_blog, apps/design-system/content, and apps/ui-library/content found 2,484 content files and 202 files missing title/description/frontmatter. The first misses are concentrated in academy course modules/labs, including:
apps/academy/content/courses/azure/azure-aks-fundamentals/lab-01-create-your-first-aks-cluster.mdapps/academy/content/courses/kubernetes/kubernetes-administrator/module-01-architecture.mdapps/academy/content/courses/kubernetes/kubernetes-administrator/lab-02-install-cluster.md
The same scan also found duplicate or templated descriptions, for example:
- 7 Azure AI Foundry labs share
Hands-on lab for the corresponding module in Azure AI Foundry Studio course. - 6 Azure AI Fundamentals labs share
Hands-on exploration of Azure AI services for the corresponding module topic. - 8 Azure Machine Learning labs share
Hands-on lab for the corresponding Azure Machine Learning module. - 4 UI Library current-user-avatar pages share
Assistance Auth-aware avataracross framework variants. - 6 UI Library dropzone pages share
Displays a control for easier uploading of files directly to Assistance Storageacross framework variants.
Recommendation: prioritize unique descriptions for course index pages, course overview pages, and public docs pages before long-tail lab modules. For framework variants, include the framework name in the description to reduce duplicate snippets while preserving consistent component taxonomy.
P2 — Some app-level metadata is thin or semantically mismatched#
The static layout review found a few layout-level descriptions that are too generic for rich snippets or use questionable Open Graph type defaults:
apps/ui-library/app/layout.tsxdescription isProvides a library of components for your project, which is thin and not specific to Assistance, frameworks, auth, realtime, or storage components.apps/ui-library/app/layout.tsxsets rootopenGraph.typetoarticleand uses current timestamps forpublishedTime/modifiedTimein app layout metadata, which can produce misleading article metadata on non-article pages.apps/design-system/app/layout.tsxandapps/ui-library/app/layout.tsxset Open Graph image URLs but no image alt text.
Recommendation: make app layout descriptions specific and evergreen, use website for root app Open Graph types, and add image alt text where layouts define OG images.
P3 — Internal links are present on major index pages, but cross-app linking should be checked in rendered output#
The static review found strong internal linking on the docs home page (apps/docs/app/page.tsx), resources page (apps/www/app/resources/page.tsx), consulting/infrastructure/runners pages, and app home pages for design-system/ui-library. Cross-app links often point to absolute URLs such as https://assistance.bg/blog from apps/www/app/resources/page.tsx, which is appropriate if these apps deploy as separate origins/subpaths but should be rendered-checked for canonical consistency.
Recommendation: use a narrow rendered crawl of top pages to confirm canonical URL, navigation links, and breadcrumb links agree with deployment paths.
Suggested next steps#
- Add route-level metadata to the high-intent routes listed in P1.
- Run rendered HTML checks for top pages to verify one
<h1>, canonical URL, and robots behavior. - Batch-generate or hand-author unique academy frontmatter descriptions for missing/duplicated course modules.
- Tighten UI Library root metadata (
description, OG type, OG image alt).
Static verification commands used#
1mise exec -- node - <<'JS'2const fs=require('fs'), path=require('path');3function walk(d){let out=[]; for(const e of fs.readdirSync(d,{withFileTypes:true})){const p=path.join(d,e.name); if(e.isDirectory()) out=out.concat(walk(p)); else if(e.name==='page.tsx') out.push(p)} return out}4for (const root of ['apps/www/app','apps/blog/app','apps/docs/app','apps/academy/app','apps/design-system/app','apps/ui-library/app']){5 console.log('\n## '+root); for(const p of walk(root).sort()){const s=fs.readFileSync(p,'utf8'); const meta=s.includes('export const metadata')||s.includes('generateMetadata'); const h1=(s.match(/<h1\\b/g)||[]).length; if(!meta||h1===0) console.log(`${p} metadata=${meta} h1=${h1}`)}6}7JS89mise exec -- node - <<'JS'10const fs=require('fs'), path=require('path');11function walk(d,exts){let out=[]; if(!fs.existsSync(d)) return out; for(const e of fs.readdirSync(d,{withFileTypes:true})){const p=path.join(d,e.name); if(e.isDirectory()) out=out.concat(walk(p,exts)); else if(exts.some(x=>e.name.endsWith(x))) out.push(p)} return out}12const files=[...walk('apps/blog/_blog',['.mdx']),...walk('apps/academy/content',['.md','.mdx']),...walk('apps/design-system/content',['.mdx']),...walk('apps/ui-library/content',['.mdx'])];13const desc=new Map(), missing=[];14for(const f of files){const s=fs.readFileSync(f,'utf8'); const fm=s.match(/^---\\n([\\s\\S]*?)\\n---/); if(!fm){missing.push(f); continue} const title=fm[1].match(/^title:\\s*[\"']?(.+?)[\"']?\\s*$/m); const d=fm[1].match(/^description:\\s*[\"']?(.+?)[\"']?\\s*$/m); if(!title||!d) missing.push(f); if(d){const v=d[1].replace(/^[\"']|[\"']$/g,''); (desc.get(v)||desc.set(v,[]).get(v)).push(f)}}15console.log('content files',files.length); console.log('missing title/description/frontmatter',missing.length); console.log(missing.slice(0,20).join('\\n'));16console.log('duplicate descriptions >1:'); for(const [d,arr] of desc) if(arr.length>1) console.log(arr.length, d, arr.slice(0,5).join(' | '));17JS