Seo Review
SEO Review Consolidated Report
Date: 2026-06-26
This report consolidates the three audit artifacts under .awao/seo-review/:
.awao/seo-review/www.md.awao/seo-review/product-apps.md.awao/seo-review/content-apps.md
It preserves findings that are supported by those artifacts and their cited file references.
Executive summary#
The audited surfaces have solid foundations in several public areas: apps/www centralizes much of its metadata, has structured data helpers, and ships robots/sitemap files; apps/blog and apps/docs use metadata helpers for canonical/social metadata; apps/manager is correctly protected from indexing with both HTML robots metadata and deployment headers.
The most urgent SEO risks are concentrated in private/internal apps and content discovery:
apps/adminis an internal admin/auth surface but lacks any noindex control, so it can be indexed if publicly deployed (apps/admin/index.html:4-7,apps/admin/src/router.tsx:15-17).apps/studiois an authenticated dashboard without global noindex controls in metadata or headers (apps/studio/pages/_app.tsx:167-172,apps/studio/next.config.ts:500-529).apps/academycan render unpublished course/content documents through its catch-all route because it does not filter matched documents bypublished(apps/academy/app/(course)/[...slug]/page.tsx:71-83).- Multiple public sitemaps are incomplete or drift-prone, especially
apps/www,apps/academy, andapps/docs. - Canonical host handling is inconsistent across public surfaces, with mixed
www.assistance.bgandassistance.bgusage inapps/wwwandapps/academy.
Global SEO risks and opportunities#
Risks#
- Private surfaces can leak into search.
apps/adminhas no HTML robots tag orX-Robots-Tagheader, andapps/studiolacks global noindex controls despite being described by route/layout evidence as an authenticated dashboard. - Sitemap coverage does not match indexable content.
apps/wwwomits key App Router landing pages,apps/academyomits most published lesson/doc/learning pages, andapps/docshas a static sitemap missing current guide URLs. - Canonical signals are inconsistent.
apps/wwwmixeshttps://www.assistance.bgwith hardcodedhttps://assistance.bg;apps/academyuses the apex host while blog/docs usewww.assistance.bg. - Some indexable pages are not intentionally controlled. UI Library example/demo routes inherit indexable app-level metadata and production headers, while Academy lesson pages are indexable but mostly absent from the sitemap.
- Metadata drift is likely where helper usage is inconsistent. Manual metadata and legacy
<Head>helpers remain inapps/www, and public docs apps lack explicit canonical URLs on inspected root/docs metadata.
Opportunities#
- Standardize canonical origins and metadata helpers across public apps to reduce drift.
- Generate sitemaps from route/content sources instead of maintaining static or legacy route lists.
- Adopt layered noindex controls for private apps: HTML metadata plus deployment-level
X-Robots-Tagheaders. - Separate public docs/showcase routes from demo/auth/dashboard routes so indexability policy matches route intent.
- Improve structured-data validity by fixing missing crawlable image assets referenced by JSON-LD.
App-by-app findings#
apps/www#
Strengths#
- Centralized metadata helper creates absolute canonical, Open Graph, and Twitter metadata from shared constants (
apps/www/lib/metadata.ts:19,apps/www/lib/metadata.ts:25,apps/www/lib/metadata.ts:27,apps/www/lib/metadata.ts:41). - Root layout sets
metadataBase, default canonical, Open Graph, Twitter card metadata, icons, and site-wide Organization JSON-LD (apps/www/app/layout.tsx:42,apps/www/app/layout.tsx:44,apps/www/app/layout.tsx:48,apps/www/app/layout.tsx:61,apps/www/app/layout.tsx:97). - Structured data helpers cover Organization, WebSite, Service, BreadcrumbList, FAQPage, and BlogPosting (
apps/www/components/JsonLd.tsx:30,apps/www/components/JsonLd.tsx:69,apps/www/components/JsonLd.tsx:96,apps/www/components/JsonLd.tsx:114,apps/www/components/JsonLd.tsx:149,apps/www/components/JsonLd.tsx:164). - Robots allow public crawling, block
/dashboard, and point to the sitemap index (apps/www/public/robots.txt:1,apps/www/public/robots.txt:3,apps/www/public/robots.txt:5). Dashboard sign-in is explicitlynoindex,nofollow(apps/www/app/dashboard/sign-in/page.tsx:7).
Findings#
- P1: Sitemap omits main App Router landing pages. The generator only glob-matches legacy
pages/*, content directories, and legacy content collections (apps/www/internals/generate-sitemap.mjs:10-17). The audit found important routes such as/services,/consulting,/infrastructure,/pricing,/resources,/runners,/trainings,/about,/contact-us, and/saasmissing fromapps/www/public/sitemap_www.xml; verification output showedcount: 87,hasHome: false,hasServices: false. - P1: Canonical/OG domains are mixed.
SITE_ORIGINcentralizeshttps://www.assistance.bg(apps/www/lib/constants.ts:34-39), but some pages hardcode apex URLs, including about (apps/www/app/about/page.tsx:13-14), SaaS (apps/www/app/saas/page.tsx:32-33), runners detail (apps/www/app/runners/[slug]/page.tsx:60-61), and case-study detail (apps/www/app/case-studies/[slug]/page.tsx:39). - P1: JSON-LD logo URL points to a missing file.
ORGANIZATION_LOGO_PATHis/images/og/assistance-logo.png(apps/www/lib/constants.ts:46), used in JSON-LD (apps/www/components/JsonLd.tsx:36,apps/www/components/JsonLd.tsx:140), but the audit found no matching asset underapps/www/public/images/og/. - P2:
/contactuses a client-side redirect.apps/www/app/contact/page.tsx:13callsrouter.replace('/contact-us'), making the redirect JavaScript-dependent. - P2: Sitemap index references a docs sitemap outside the app output.
apps/www/public/sitemap.xml:7andapps/www/internals/generate-sitemap.mjs:139referencehttps://www.assistance.bg/docs/sitemap.xml, while the checkedapps/www/outonly includedsitemap.xmlandsitemap_www.xml. - P2: Legacy/manual metadata increases drift risk.
apps/www/lib/seo.tsxstill emits<Head>tags manually, andapps/www/app/(pages)/beta/BetaPage.tsxusesnext/head.
apps/admin#
- P0: Internal admin app can be indexed if deployed publicly.
apps/admin/index.html:4-7lacks<meta name="robots" content="noindex, nofollow">;apps/admin/src/router.tsx:15-17shows login/authenticated app routes;apps/admin/vite.config.ts:31-32exposes dev/preview hosts; and the audit found noapps/admin/public/_headersfile.
apps/studio#
- P1: Authenticated dashboard lacks global noindex controls. Global metadata in
apps/studio/pages/_app.tsx:167-172does not setrobots=noindex,nofollow; authenticated layouts set titles/descriptions without noindex (apps/studio/components/layouts/ProjectLayout/index.tsx:218-220,apps/studio/components/layouts/OrganizationLayout.tsx:60-63); global headers inapps/studio/next.config.ts:500-529omitX-Robots-Tag; and no robots/sitemap files were found in the audit.
apps/manager#
- Positive control:
apps/manager/index.html:33-35includesrobots=noindex,nofollow, andapps/manager/public/_headers:1-6blocks indexing withX-Robots-Tagwhile adding security headers. apps/manager/vite.config.ts:11-20replaces title/description placeholders with brand-aware text at build time.
apps/design-system#
- P1: Public docs app has no first-party robots.txt or sitemap. The audit found no
robots.*orsitemap.*under the app, while the app appears intended to be indexable through root metadata (apps/design-system/app/layout.tsx:15-32), per-doc metadata (apps/design-system/app/(app)/docs/[[...slug]]/page.tsx:45-64), and production_headersthat allow indexing (apps/design-system/public/_headers:1-5). - P2: Canonical/OG URL handling should be tightened. Root metadata uses
metadataBase: https://assistance.bgand a relative Open Graph URL (apps/design-system/app/layout.tsx:19-25). Per-doc metadata generates Open Graph URLs (apps/design-system/app/(app)/docs/[[...slug]]/page.tsx:52), but noalternates.canonicalwas found in inspected metadata.
apps/ui-library#
- P1: Public docs app has no first-party robots.txt or sitemap. The audit found no
robots.*orsitemap.*; root and doc metadata are indexable (apps/ui-library/app/layout.tsx:17-36,apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx:45-55), and production_headersexplicitly allow indexing (apps/ui-library/public/_headers:1-5). - P1: Example/demo routes are probably indexable. Example layouts such as
apps/ui-library/app/example/password-based-auth/layout.tsx:6-8set only a title; app-level metadata is indexable by default; and production headers allow indexing for the whole domain (apps/ui-library/public/_headers:1-5). - P2: Root metadata uses article semantics and unstable timestamps.
apps/ui-library/app/layout.tsx:23-24setsopenGraph.type: 'article', andapps/ui-library/app/layout.tsx:28-29setspublishedTime/modifiedTimefromnew Date().toISOString(). - P2: Canonical/OG URL handling should be tightened. Root metadata uses
metadataBase: https://assistance.bgand relative Open Graph URL (apps/ui-library/app/layout.tsx:21-27); per-doc Open Graph URLs are generated atapps/ui-library/app/(app)/docs/[[...slug]]/page.tsx:54, but no inspected metadata declaredalternates.canonical.
apps/academy#
- P1: Unpublished course lessons can render through the catch-all route.
getPageFromParamsreads Contentlayer documents (apps/academy/app/(course)/[...slug]/page.tsx:71) and matchesallCourses,allLearnings, andallDocswithout checkingpublished(apps/academy/app/(course)/[...slug]/page.tsx:76,apps/academy/app/(course)/[...slug]/page.tsx:79,apps/academy/app/(course)/[...slug]/page.tsx:83). Contentlayer definespublishedfields/defaults (apps/academy/contentlayer.config.js:116,apps/academy/contentlayer.config.js:187,apps/academy/contentlayer.config.js:236). The audit found two explicitly unpublished course files:apps/academy/content/courses/nextjs/api-development/lab-04-database.mdandapps/academy/content/courses/svelte/sveltekit-fullstack/lab-02-blog-dynamic-routes.md. - P1: Sitemap omits most indexable content pages.
apps/academy/app/sitemap.ts:8includes onlyallCourses.filter((c) => c.isIndex && c.published), andapps/academy/app/sitemap.ts:18derives category pages from that subset. The audit counted 125 published course index pages, 1,963 published course lesson/lab pages, 21 learning articles, and 12 foundation/platform docs not represented by current sitemap logic. - P2: Canonical host differs from blog/docs. Blog defaults to
https://www.assistance.bg/blog(apps/blog/lib/constants.ts:1), docs useshttps://www.assistance.bg${BASE_PATH}(apps/docs/lib/constants.ts:14), while academy uses the apex host in metadata/sitemap helpers (apps/academy/app/layout.tsx:29,apps/academy/lib/seo.ts:3,apps/academy/app/sitemap.ts:4). - Positive control: protected academy routes explicitly noindex via
apps/academy/app/(protected)/layout.tsx:7; training/category pages usebuildSeoMetadataand ItemList JSON-LD (apps/academy/app/(trainings)/page.tsx:19,apps/academy/app/(trainings)/[category]/page.tsx:15).
apps/docs#
- P1: Metadata can emit
noindex,nofollowoutside Vercel production.apps/docs/lib/constants.ts:10defines production fromNEXT_PUBLIC_VERCEL_ENV === 'production'; root metadata uses that flag for robots (apps/docs/app/layout.tsx:29-31);apps/docs/public/robots.txt:1allows crawling, so HTML robots metadata can suppress indexing if the env var is missing or differs in production. - P2: Static sitemap is missing current guide content.
apps/docs/public/sitemap.xmlhas 201<loc>entries, while content scan found 212 guide pages. The static sitemap is missing 12 guide URLs listed in the source artifact, andapps/docs/public/robots.txt:3points crawlers at this static sitemap. - Positive control: guide pages generate metadata from MDX frontmatter via
genGuideMeta(apps/docs/features/docs/GuidesMdx.utils.tsx:202) and use canonical resolution (apps/docs/lib/seo.ts:22).
apps/blog#
- P2: Sitemap uses request/build time for taxonomy and root
lastModified. Category entries usenew Date()(apps/blog/app/sitemap.ts:15-17), tag entries usenew Date()(apps/blog/app/sitemap.ts:22-24), and the blog root usesnew Date()(apps/blog/app/sitemap.ts:31-32). - Positive control: root and article metadata use a shared helper with canonical, Open Graph, and Twitter metadata (
apps/blog/lib/seo.ts:83,apps/blog/app/[slug]/page.tsx:41). Articles havedynamicParams = false(apps/blog/app/[slug]/page.tsx:12) and JSON-LD for BlogPosting/breadcrumbs.
Prioritized remediation checklist#
P0#
- Add layered noindex protection to
apps/admin:- Add
<meta name="robots" content="noindex, nofollow">toapps/admin/index.html. - Add
apps/admin/public/_headerswithX-Robots-Tag: noindex, nofollow, following the pattern inapps/manager/public/_headers:1-6.
- Add
P1#
- Add global noindex controls for authenticated
apps/studiodashboard routes, preferably viaX-Robots-Taginapps/studio/next.config.ts; document any intentional public exceptions. - Filter unpublished
apps/academyContentlayer matches inapps/academy/app/(course)/[...slug]/page.tsxsopublished === falsereturnsnotFound(). - Rework
apps/wwwsitemap generation to include App Router landing pages,/, and primary conversion pages while excluding private/test/client-only redirect routes. - Expand
apps/academy/app/sitemap.tsto include all published publicallCourses,allLearnings, andallDocs, or explicitly noindex content types that should not be search landing pages. - Replace or generate
apps/docs/public/sitemap.xmlfrom current content so guide additions cannot drift from the sitemap. - Add
app/robots.tsandapp/sitemap.tstoapps/design-systemandapps/ui-libraryfor public docs/showcase discovery. - Add noindex metadata to
apps/ui-library/app/example/*demo layouts or a shared example layout segment. - Standardize canonical host usage for
apps/wwwandapps/academy; update hardcoded apex/www URLs to shared constants once the canonical host is confirmed. - Fix
apps/wwwJSON-LD logo path by addingapps/www/public/images/og/assistance-logo.pngor updatingORGANIZATION_LOGO_PATHto an existing crawlable asset. - Make
apps/docsproduction indexability controlled by an explicit app-owned environment contract instead of onlyNEXT_PUBLIC_VERCEL_ENV.
P2#
- Replace
apps/www/app/contact/page.tsxclient-side redirect with a server/static redirect compatible with the static export deployment. - Verify production serves
https://www.assistance.bg/docs/sitemap.xml; remove the reference fromapps/www/public/sitemap.xmlif not served. - Migrate remaining
apps/wwwmanual/legacy<Head>usage to App Router metadata or isolate legacy helpers. - Set
apps/ui-libraryroot Open Graph type towebsiteand remove dynamic rootpublishedTime/modifiedTimevalues. - Add explicit
alternates.canonicalmetadata forapps/design-systemandapps/ui-libraryroot/docs pages. - Compute
apps/blogtaxonomy/root sitemaplastModifiedfrom newest relevant published posts instead ofnew Date().
Verification commands and manual checks#
Use mise exec -- ... for one-off raw tools that need managed runtimes, per repo guidance.
Sitemap and route coverage checks#
1mise exec -- node -e "const fs=require('fs'); const xml=fs.readFileSync('apps/www/public/sitemap_www.xml','utf8'); const urls=[...xml.matchAll(/<loc>(.*?)<\/loc>/g)].map(m=>m[1]); console.log({count:urls.length, hasHome:urls.includes('https://www.assistance.bg/'), hasServices:urls.includes('https://www.assistance.bg/services'), hasBlog:urls.some(u=>u.includes('/blog/')), hasCaseStudy:urls.some(u=>u.includes('/case-studies/'))});"1mise exec -- node -e "const fs=require('fs'); const xml=fs.readFileSync('apps/docs/public/sitemap.xml','utf8'); console.log((xml.match(/<loc>/g)||[]).length);"Manual checks:
- Confirm all primary
apps/wwwlanding pages are present in the generated production sitemap after remediation. - Confirm
https://www.assistance.bg/docs/sitemap.xmlis served in production if it remains referenced byapps/www/public/sitemap.xml. - Confirm generated docs/academy/design-system/ui-library sitemaps include current content routes and exclude private/demo routes as intended.
Noindex/private-surface checks#
1rg -n "robots|X-Robots-Tag|noindex|nofollow" apps/admin apps/manager apps/studioManual checks:
- Deploy or preview
apps/adminandapps/studio, then inspect response headers forX-Robots-Tag: noindex, nofollow. - View rendered HTML for private/auth routes and verify
<meta name="robots" content="noindex, nofollow">where applicable. - Verify
apps/managerretains its existing noindex behavior.
Canonical and Open Graph checks#
1rg -n "https://assistance\.bg|https://www\.assistance\.bg|alternates|canonical|openGraph" apps/www apps/academy apps/blog apps/docs apps/design-system apps/ui-libraryManual checks:
- Confirm the intended canonical host is enforced consistently in metadata, sitemaps, structured data, and redirects.
- Inspect public pages with browser devtools or rendered HTML to verify canonical and Open Graph URLs are absolute and host-consistent.
Structured data and asset checks#
1find apps/www/public/images -path '*og*' -o -name '*logo*'2rg -n "ORGANIZATION_LOGO_PATH|logo" apps/www/lib apps/www/componentsManual checks:
- Validate representative public pages with Google's Rich Results Test or schema validator after fixing the logo URL.
- Confirm referenced JSON-LD image/logo URLs return crawlable 200 responses with appropriate image dimensions.
Content publication checks#
1rg -n "published" apps/academy/app apps/academy/contentlayer.config.js2find apps/academy/content/courses -name '*.md' -o -name '*.mdx' | wc -lManual checks:
- Request known unpublished academy URLs, including
apps/academy/content/courses/nextjs/api-development/lab-04-database.mdandapps/academy/content/courses/svelte/sveltekit-fullstack/lab-02-blog-dynamic-routes.mdURL equivalents, and verify they return 404/not found after remediation. - Confirm published lessons/docs that should be public are either included in the sitemap or explicitly noindexed by product decision.