Services

Apps Ui Library


SEO review: apps/ui-library

Scope: apps/ui-library Next.js app, including App Router metadata, docs/registry routes, ContentLayer content, structured data, robots/sitemap behavior, headings/content semantics, and public assets.

Summary#

apps/ui-library has a solid baseline for title/description metadata, Open Graph/Twitter images, generated ContentLayer docs pages, semantic article headings, favicon assets, registry JSON assets, and llms.txt. The main SEO gaps are crawl discovery/control and canonical URL consistency, especially because the app appears to live under /ui while metadata is split between metadataBase, NEXT_PUBLIC_BASE_PATH, and NEXT_PUBLIC_APP_URL.

High severity#

Missing robots.txt and sitemap generation#

Evidence

  • No robots.* or sitemap.* App Router metadata files exist under apps/ui-library/app.
  • No robots.txt or sitemap.xml files exist under apps/ui-library/public.
  • The build scripts generate registry JSON and llms.txt, but no sitemap/robots artifact: apps/ui-library/package.json:9-12.
  • Cloudflare/Vercel headers only set X-Robots-Tag for specific hosts and assets; they do not publish crawl entrypoints: apps/ui-library/public/_headers:1-27.

Impact

Search engines have no app-local sitemap listing the generated docs routes, registry/docs URLs, or canonical app base. Preview blocking is header-based only, and production crawl policy depends on deployment host matching _headers.

Recommended fix

Add app/robots.ts and app/sitemap.ts that use the same canonical base URL and base path as metadata. Include the home page and all published ContentLayer docs pages; exclude /example/*, preview-only hosts, and generated registry JSON unless those assets are intentionally indexable.

Canonical URLs are not emitted for home or docs pages#

Evidence

  • Root metadata defines metadataBase, title/description, Open Graph, and Twitter metadata, but no alternates.canonical: apps/ui-library/app/layout.tsx:17-37.
  • Docs metadata overrides title/description and Open Graph URL, but also omits alternates.canonical: apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx:37-57.
  • ContentLayer produces stable slugs for every doc (/${doc._raw.flattenedPath}), but those slugs are only used for route matching and OG URL generation: apps/ui-library/contentlayer.config.js:11-20.

Impact

The same content can be reachable through different deployment hosts, with or without /ui, or through framework-specific near-duplicate pages. Without canonical tags, search engines must infer the preferred URL.

Recommended fix

Centralize a siteUrl/basePath helper and set alternates.canonical in root and docs metadata. For docs pages, canonical should resolve from each doc.slug using the production URL, not from a request-time or optional env value.

Medium severity#

Absolute URL generation can produce invalid metadata if NEXT_PUBLIC_APP_URL is unset or mismatched#

Evidence

  • Docs Open Graph URL uses absoluteUrl(doc.slug): apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx:49-55.
  • absoluteUrl concatenates process.env.NEXT_PUBLIC_APP_URL directly: apps/ui-library/lib/utils.ts:5-7.
  • Root metadata separately hard-codes metadataBase: new URL('https://assistance.bg') and derives OG image paths from NEXT_PUBLIC_BASE_PATH: apps/ui-library/app/layout.tsx:15-27.
  • llms.txt assumes production docs live at https://assistance.bg/ui/docs: apps/ui-library/scripts/build-llms-txt.ts:4.

Impact

If NEXT_PUBLIC_APP_URL is absent, stale, or does not include /ui, docs pages can emit malformed or non-canonical og:url values while other assets point at https://assistance.bg/ui/....

Recommended fix

Use one canonical URL source for metadataBase, absoluteUrl, sitemap, robots, and llms.txt. Fail builds when the production URL is missing, or provide a checked default that includes the intended base path.

Draft/unpublished ContentLayer documents are not filtered#

Evidence

  • ContentLayer supports a published field with default true: apps/ui-library/contentlayer.config.js:69-72.
  • Docs route lookup searches all docs without checking published: apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx:26-34.
  • Static params include every doc from allDocs: apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx:60-64.
  • llms.txt recursively includes every .mdx file with a title and does not parse/filter published: apps/ui-library/scripts/build-llms-txt.ts:47-75.

Impact

Any future published: false draft would still be statically generated, discoverable in llms.txt, and eligible for sitemap inclusion if a sitemap later mirrors the current route behavior.

Recommended fix

Create a shared publishedDocs = allDocs.filter((doc) => doc.published) helper and use it for route lookup, static params, sitemap, navigation/search where applicable, and llms.txt generation.

Home page is forced dynamic with no revalidation#

Evidence

  • The home route exports dynamic = 'force-dynamic' and revalidate = 0: apps/ui-library/app/(app)/page.tsx:10-11.
  • The dynamic behavior appears to be driven by a randomized demo room name: apps/ui-library/app/(app)/page.tsx:13-15.

Impact

The landing page cannot be fully statically optimized and may be less cacheable for crawlers. The random demo URL also changes rendered markup between requests.

Recommended fix

Move randomized demo state into a client-only demo component or use a deterministic room identifier so the index page can be statically rendered and cached.

No structured data for docs or component registry pages#

Evidence

  • No application/ld+json, schema.org, or JSON-LD structured data references were found in apps/ui-library.
  • Docs pages render rich doc content with title, description, breadcrumbs, TOC, and source panel, but no Article, TechArticle, BreadcrumbList, or SoftwareSourceCode schema: apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx:76-111.

Impact

Search engines can index the pages, but the app misses enhanced understanding for technical documentation, breadcrumbs, and reusable component/code resources.

Recommended fix

Add per-doc JSON-LD for TechArticle and BreadcrumbList. Consider SoftwareSourceCode or CreativeWork for registry-backed component pages where source metadata is available.

Low severity#

OG/Twitter metadata exists but is generic#

Evidence

  • Root metadata sets a static OG/Twitter image at ${BASE_PATH}/img/assistance-og-image.png: apps/ui-library/app/layout.tsx:23-35.
  • The image asset exists at apps/ui-library/public/img/assistance-og-image.png and is the correct 1200x630 size.
  • Docs metadata overrides title/description but inherits the same generic image: apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx:45-56.

Impact

Social previews should work, but every docs page shares the same image and root publishedTime/modifiedTime values are generated at runtime/build time rather than derived from content: apps/ui-library/app/layout.tsx:28-29.

Recommended fix

Keep the current image as a fallback, but add deterministic per-doc OG images or remove article timestamps unless real content dates are available.

Evidence

  • The footer announces a visually hidden <h2> and then renders footer section titles as <h6>: apps/ui-library/components/site-footer.tsx:33-45.
  • The main docs page uses one visible <h1> for the document title before MDX content: apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx:84-98.

Impact

This is mostly an accessibility/semantics issue, but cleaner heading order helps crawlers and assistive technology understand page structure.

Recommended fix

Use non-heading text for footer column labels or change them to <h3> under the footer <h2>.

Positive findings#

  • Root layout sets lang="en", application name, title, description, metadata base, favicon metadata, Open Graph, and Twitter card metadata: apps/ui-library/app/layout.tsx:17-37 and apps/ui-library/app/layout.tsx:49-65.
  • Docs pages derive metadata from required ContentLayer frontmatter fields: apps/ui-library/contentlayer.config.js:56-68 and apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx:45-57.
  • Static params are generated for ContentLayer docs, enabling pre-rendering of docs routes: apps/ui-library/app/(app)/docs/[[...slug]]/page.tsx:60-64.
  • MDX rendering preserves semantic headings, paragraphs, lists, tables, blockquotes, and image alt attributes from content: apps/ui-library/components/mdx-components.tsx:28-117.
  • Preview deployments are protected from indexing through X-Robots-Tag: noindex, nofollow: apps/ui-library/public/_headers:20-27.
  • llms.txt is generated and published as a machine-readable docs index: apps/ui-library/scripts/build-llms-txt.ts:78-106.