Skip to content

Enhance Metadata and SEO#140

Merged
chef0111 merged 4 commits intostagingfrom
feat/seo
Feb 2, 2026
Merged

Enhance Metadata and SEO#140
chef0111 merged 4 commits intostagingfrom
feat/seo

Conversation

@chef0111
Copy link
Copy Markdown
Owner

@chef0111 chef0111 commented Feb 2, 2026

Summary by CodeRabbit

  • New Features

    • Dynamic Open Graph/Twitter images for sharing
    • Dynamic sitemap generation for improved discovery
    • robots configuration to guide crawlers
    • Per-page SEO metadata (users, questions, Home, Community, Tags) with canonical URLs and social cards
  • Enhancements

    • Expanded site-wide metadata and structured data for better SEO and link previews

@vercel
Copy link
Copy Markdown

vercel bot commented Feb 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dev4room Ready Ready Preview, Comment Feb 2, 2026 10:50am

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 2, 2026

Warning

Rate limit exceeded

@chef0111 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 44 minutes and 21 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

Walkthrough

Adds comprehensive SEO features: dynamic metadata generators for user/question pages, updated page metadata, Open Graph/Twitter image generators, robots and sitemap routes, and a shared baseUrl constant used across metadata and structured data.

Changes

Cohort / File(s) Summary
Dynamic Metadata
src/app/(root)/(account)/[username]/page.tsx, src/app/(root)/(dashboard)/questions/[id]/page.tsx
Added generateMetadata exports that query the database for user/question data and assemble title, description, Open Graph, Twitter, images, and canonical alternates with not-found fallbacks.
Page Metadata Updates
src/app/(root)/(dashboard)/(home)/page.tsx, src/app/(root)/(dashboard)/community/page.tsx, src/app/(root)/(dashboard)/tags/page.tsx
Reworked exported metadata: simplified titles, added Open Graph and Twitter blocks, and canonical alternates using baseUrl.
Root Layout Metadata & JSON‑LD
src/app/layout.tsx
Expanded global metadata (metadataBase, title template, openGraph, twitter, robots, verification) and updated JSON‑LD structured data and navigation items to use baseUrl.
OG / Twitter Image Generators
src/app/opengraph-image.tsx, src/app/twitter-image.tsx
New edge-runtime image endpoints returning ImageResponse (1200x630) JSX compositions used for social previews.
Robots & Sitemap
src/app/robots.ts, src/app/sitemap.ts
Added robots route returning robots rules and sitemap generator that emits static and dynamic URLs (questions, user profiles) by querying the DB.
Constants
src/common/constants/index.ts
Added exported baseUrl constant sourced from NEXT_PUBLIC_APP_URL with a fallback URL used throughout metadata and routes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nibble bytes and stitch a tune,
Tags and sitemaps under moon,
OG images polished bright,
Robots hum into the night,
Hop—our links now shine in light ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary objective of the PR—enhancing metadata and SEO across multiple pages and creating SEO infrastructure files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/seo

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Owner Author

@chef0111 chef0111 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/app/`(root)/(dashboard)/questions/[id]/page.tsx:
- Around line 17-63: Replace the hardcoded "https://dev4room.pro" in
generateMetadata with the shared baseUrl constant: import baseUrl from (or named
import) "@/common/constants" at top, then use `${baseUrl}/questions/${id}` for
openGraph.url and alternates.canonical; update the openGraph and alternates
assignments in the generateMetadata function (identify by function name
generateMetadata and properties openGraph.url and alternates.canonical) so URLs
follow NEXT_PUBLIC_APP_URL consistently.

In `@src/app/layout.tsx`:
- Line 120: The manual <link rel="canonical" href={baseUrl} /> in
src/app/layout.tsx conflicts with Next.js metadataBase and per-page
alternates.canonical; remove this hardcoded canonical tag so Next's metadata
system (metadataBase and alternates.canonical) can generate page-specific
canonical URLs. Locate and delete the JSX node rendering <link rel="canonical"
href={baseUrl} /> (referenced as baseUrl in the layout component) to avoid
applying a global canonical to all pages.

In `@src/app/twitter-image.tsx`:
- Line 140: The displayed URL "dev4room.pro" is hardcoded in the TwitterImage
component; replace it with the central baseUrl constant by deriving the host
from baseUrl (e.g., new URL(baseUrl).host or strip the protocol) and render that
value instead so the component uses the shared baseUrl constant rather than a
literal string.
🧹 Nitpick comments (6)
src/app/opengraph-image.tsx (1)

1-147: Consider extracting shared image rendering logic.

This file is nearly identical to src/app/twitter-image.tsx (based on the relevant code snippets). While Next.js requires separate opengraph-image.tsx and twitter-image.tsx files for the route convention, the rendering JSX could be extracted into a shared component to reduce duplication.

♻️ Suggested refactor

Create a shared component:

// src/app/_shared/social-image-content.tsx
export function SocialImageContent() {
  return (
    <div style={{ /* ... shared styles ... */ }}>
      {/* Logo, Title, Tagline, Features, URL */}
    </div>
  );
}

Then both image files can import and use it:

+import { SocialImageContent } from "./_shared/social-image-content";
+
 export default async function Image() {
   return new ImageResponse(
-    <div style={{ /* ... 100+ lines of JSX ... */ }}>
-      {/* ... */}
-    </div>,
+    <SocialImageContent />,
     { ...size }
   );
 }
src/app/sitemap.ts (2)

46-60: Same scalability consideration applies to user queries.

The user query also fetches all verified, non-banned users without pagination. The same concerns about memory and timeouts apply here as the user base grows.


30-44: Add limits to database queries for scalability.

Both the questions and users queries fetch all matching records without pagination. While the data is cached and revalidated every 30-60 seconds, this can cause memory pressure as the dataset grows.

For large sitemaps approaching 50,000+ URLs, implement sitemap index files and chunk the queries using .limit() and .offset(), following the pattern used elsewhere in the codebase.

src/app/(root)/(dashboard)/questions/[id]/page.tsx (1)

39-42: Consider stripping HTML/markdown from content before truncation.

The content field likely contains HTML or markdown formatting. Truncating raw content could result in:

  • Broken HTML tags in the description (e.g., <p>Some text...)
  • Markdown syntax appearing in social previews (e.g., ## Header...)
♻️ Example approach
// Strip HTML tags and normalize whitespace
const plainText = questionData.content
  .replace(/<[^>]*>/g, '')
  .replace(/\s+/g, ' ')
  .trim();

const description = plainText.length > 160
  ? plainText.slice(0, 157) + "..."
  : plainText;
src/app/(root)/(dashboard)/tags/page.tsx (1)

12-30: Consider extracting the repeated description string.

The description is duplicated three times. Extracting it to a constant improves maintainability and ensures consistency if the text needs to change.

♻️ Suggested refactor
+const pageDescription =
+  "Explore tags on Dev4Room to find questions and experts by technology, language, and topic. Discover trending tags and related content.";
+
 export const metadata: Metadata = {
   title: "Tags",
-  description:
-    "Explore tags on Dev4Room to find questions and experts by technology, language, and topic. Discover trending tags and related content.",
+  description: pageDescription,
   openGraph: {
     title: "Tags | Dev4Room",
-    description:
-      "Explore tags on Dev4Room to find questions and experts by technology, language, and topic. Discover trending tags and related content.",
+    description: pageDescription,
     url: `${baseUrl}/tags`,
   },
   twitter: {
     title: "Tags | Dev4Room",
-    description:
-      "Explore tags on Dev4Room to find questions and experts by technology, language, and topic. Discover trending tags and related content.",
+    description: pageDescription,
   },
   alternates: {
     canonical: `${baseUrl}/tags`,
   },
 };
src/app/twitter-image.tsx (1)

1-147: Consider extracting shared image template with opengraph-image.tsx.

This file is nearly identical to src/app/opengraph-image.tsx (same layout, styles, and content). Both files define the same visual design with duplicated JSX. Consider extracting the shared rendering logic into a common utility to maintain consistency and reduce duplication.

♻️ Example approach

Create a shared image template:

// src/lib/og/shared-image-template.tsx
export function createBrandImage(size: { width: number; height: number }) {
  return new ImageResponse(
    // ... shared JSX ...
    { ...size }
  );
}

Then both image files can import and use this:

// src/app/twitter-image.tsx
import { createBrandImage } from "@/lib/og/shared-image-template";

export const size = { width: 1200, height: 630 };
export default async function Image() {
  return createBrandImage(size);
}

Comment thread src/app/(root)/(dashboard)/questions/[id]/page.tsx
Comment thread src/app/layout.tsx Outdated
Comment thread src/app/twitter-image.tsx Outdated
Copy link
Copy Markdown
Owner Author

@chef0111 chef0111 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant