Apps Blog
SEO review: apps/blog
Date: 2026-07-01
Scope: apps/blog Next.js app metadata, article/list templates, canonical/OG/Twitter tags, robots/sitemap/RSS behavior, search/filter UX, structured data, heading/content semantics, and public assets.
Summary#
apps/blog has a solid SEO foundation: central metadata helpers, static article params, blog-scoped canonical URLs, robots/sitemap/RSS routes, article/blog/breadcrumb JSON-LD, and crawlable article pages. The main risks are asset and URL correctness issues that can break social previews or route users/crawlers to blog-local paths that should resolve outside the /blog app.
Critical findings#
1. Default OG image points to a missing public asset#
Evidence
apps/blog/lib/seo.ts:16setsDEFAULT_OG_IMAGE = '/images/og/assistance-og.png'.apps/blog/app/layout.tsx:25-29uses that value for default Open Graph images.apps/blog/lib/seo.ts:64-67andapps/blog/lib/seo.ts:93-107use the same image for post/page metadata fallbacks.apps/blog/components/JsonLd.tsx:76uses the same fallback forBlogPosting.image.- Public asset inspection found no
apps/blog/public/images/og/assistance-og.png;apps/blog/public/images/blog/assistance-og-image.pngexists instead.
Impact
Pages without per-post images publish broken og:image, Twitter image, RSS enclosure, and JSON-LD image URLs. Social shares may render without an image and structured-data validation may warn about an inaccessible image.
Recommended fix
Add the expected public/images/og/assistance-og.png asset or update DEFAULT_OG_IMAGE to an existing, blog-scoped image. Then validate one home page and one article with generated metadata or rendered HTML.
High severity findings#
1. Blog CTAs and legacy service redirects use blog-local service/contact URLs#
Evidence
- The home CTA links to
/services/devops-as-a-serviceand/contact-usinapps/blog/app/BlogClient.tsx:40-49. - The post template CTA component is rendered on every article in
apps/blog/components/Blog/BlogPostRenderer.tsx:305. - The legacy services page redirects to
/services/${slug}inapps/blog/app/services/[slug]/page.tsx:23-35, while its metadata canonical is also/services/${slug}inapps/blog/app/services/[slug]/page.tsx:13-18.
Impact
Because the app is configured with basePath: '/blog' in apps/blog/next.config.mjs:38, relative URLs can resolve as /blog/services/... and /blog/contact-us. That creates noindex intermediate pages for services and likely broken or duplicate blog-local paths instead of sending users and crawlers to the main site service/contact pages.
Recommended fix
Use absolute main-site URLs for cross-app destinations, e.g. https://www.assistance.bg/services/devops-as-a-service and https://www.assistance.bg/contact-us, and make legacy service redirects/canonicals point at the canonical main-site service URLs.
2. Category and tag listing markup uses invalid children under ordered lists#
Evidence
- Category pages render
<ol>with direct<div>children inapps/blog/app/categories/[category]/page.tsx:63-71. - Tag pages render the same invalid pattern in
apps/blog/app/tags/[tag]/page.tsx:55-63. - The main blog list uses valid
<ol>/<li>structure inapps/blog/app/BlogClient.tsx:138-153, which is a good local reference.
Impact
Invalid list semantics weaken machine readability and accessibility for taxonomy listing pages. Search engines generally recover, but the pages are less semantically consistent than the main listing.
Recommended fix
Replace the direct <div> children with <li> wrappers matching the main blog list pattern.
Medium severity findings#
1. Taxonomy URLs are both crawlable and listed in the sitemap, but search/filter states are not canonicalized or noindexed#
Evidence
- Sitemap includes every category and tag URL in
apps/blog/app/sitemap.ts:15-27. - Category and tag pages have generated metadata/canonicals in
apps/blog/app/categories/[category]/page.tsx:21-34andapps/blog/app/tags/[tag]/page.tsx:21-34. - Client-side filters also mutate the home URL to
/?q=...and/?category=...inapps/blog/components/Blog/BlogFilters.tsx:61-75andapps/blog/components/Blog/BlogFilters.tsx:92-107.
Impact
Dedicated taxonomy pages are indexable, while query-parameter filter states on the home page can also be discovered through shared URLs. The home metadata canonical remains /, so filtered URLs consolidate to the home page, but they are still crawlable parameter variants with no explicit robots policy.
Recommended fix
Keep crawlable taxonomy pages as the primary indexable category/tag surfaces. For ?q= and ?category= UI states, either avoid exposing shareable parameter URLs, add middleware/metadata that emits noindex,follow for search-result states, or document that canonical-to-home is intentional.
2. RSS feed omits lastBuildDate and uses a generic author value#
Evidence
- RSS route emits channel metadata and items in
apps/blog/app/rss.xml/route.ts:33-43, but no channellastBuildDate. - Item authors are rendered as plain display names in
apps/blog/app/rss.xml/route.ts:17-28.
Impact
The feed is valid enough for many readers, but lastBuildDate helps feed clients detect updates. Some RSS consumers expect <author> to contain an email-like value; display-only names may be ignored.
Recommended fix
Add lastBuildDate from the newest post date. Consider dc:creator for display author names or a valid RSS author format if email addresses are available.
Low severity findings#
1. Homepage structured data does not expose SearchAction#
Evidence
getWebSiteSchema()returnsWebSitewith name, URL, description, and publisher inapps/blog/components/JsonLd.tsx:16-24.- The blog has client-side search in
apps/blog/components/Blog/BlogFilters.tsx:174-200.
Impact
Search engines can understand the site, but the WebSite schema does not describe the available blog search behavior. This is not required, especially because search is client-side, but it is a missed enhancement.
Recommended fix
If the ?q= search URL is intentionally supported, add a potentialAction SearchAction to WebSite JSON-LD. If filtered query pages should not be indexed, skip this and noindex search states instead.
2. BlogPosting JSON-LD could be richer#
Evidence
- Article JSON-LD includes headline, description, URL, mainEntityOfPage, image, dates, author, publisher, and keywords in
apps/blog/components/JsonLd.tsx:58-85. - Article pages pass post frontmatter into that schema in
apps/blog/app/[slug]/page.tsx:135-148.
Impact
The schema covers required basics. It could better match rich-result recommendations by adding fields such as isPartOf, articleSection, and author URLs/images where available.
Recommended fix
Extend getBlogPostingSchema() with available category, tag, and author profile/image data after the image URL issue is fixed.
Positive observations#
- The app has a blog-scoped default origin (
https://www.assistance.bg/blog) inapps/blog/lib/constants.ts:1, and tests assert canonical/robots defaults inapps/blog/test/seo-routes.test.ts:7-23. - Root metadata sets
metadataBase, title templates, description, RSS alternate, Open Graph defaults, and Twitter card defaults inapps/blog/app/layout.tsx:13-34. - Page/article metadata is centralized via
createPageMetadata()with canonical, Open Graph, Twitter, and keyword support inapps/blog/lib/seo.ts:83-116. - Article routes are statically enumerated and unknown slugs are disabled via
generateStaticParams()anddynamicParams = falseinapps/blog/app/[slug]/page.tsx:11-12andapps/blog/app/[slug]/page.tsx:34-39. - Articles render a single visible
<h1>and an<article>container inapps/blog/components/Blog/BlogPostRenderer.tsx:162andapps/blog/components/Blog/BlogPostRenderer.tsx:215-251. - Robots and sitemap routes exist and point at the blog-scoped sitemap in
apps/blog/app/robots.ts:5-12andapps/blog/app/sitemap.ts:6-40. - RSS is discoverable from metadata and served with the correct content type/cache headers in
apps/blog/app/layout.tsx:21-24andapps/blog/app/rss.xml/route.ts:45-49. - Preview-domain
_headersblock indexing while production blog routes allow indexing inapps/blog/public/_headers:1-32.