Skip to content

feat: Dynamic SEO metadata, OpenGraph images, and social sharing for articles#5

Merged
halilibrahimcelik merged 2 commits into
development-05from
copilot/enhance-seo-performance-articles
Mar 17, 2026
Merged

feat: Dynamic SEO metadata, OpenGraph images, and social sharing for articles#5
halilibrahimcelik merged 2 commits into
development-05from
copilot/enhance-seo-performance-articles

Conversation

Copy link
Copy Markdown

Copilot AI commented Mar 17, 2026

Article pages lacked SEO metadata entirely and had no mechanism for social sharing or link previews.

Changes

Dynamic generateMetadataapp/wiki/[id]/page.tsx

  • Per-article <title>, <meta description>, full OpenGraph (article type with publishedTime, authors, images), Twitter Card (summary_large_image), and canonical URL
  • Falls back gracefully when article is not found

Placeholder OpenGraph image — app/wiki/[id]/opengraph-image.tsx

  • New edge-runtime ImageResponse (1200×630) auto-served at /wiki/[id]/opengraph-image
  • Renders article title, stripped-markdown excerpt, author avatar initial, and WikiMasters branding
  • Overridden in metadata when the article has its own imageUrl

Social sharing buttons — wiki-article-viewer.tsx

  • Footer share row: Twitter/X, Facebook, LinkedIn (open platform share dialogs via window.open), plus Copy Link (clipboard with toast feedback)
  • All share URLs use encodeURIComponent

Shared utility — lib/utils.ts

export function stripMarkdown(content: string, maxLength = 160): string

Deduplicated markdown-stripping logic used by both generateMetadata and the OG image generator.


💬 Send tasks to Copilot coding agent from Slack and Teams to turn conversations into code. Copilot posts an update in your thread when it's finished.

@vercel
Copy link
Copy Markdown

vercel Bot commented Mar 17, 2026

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

Project Deployment Actions Updated (UTC)
wikimasters Error Error Mar 17, 2026 0:46am

… sharing

Co-authored-by: halilibrahimcelik <92088301+halilibrahimcelik@users.noreply.github.com>
Copilot AI changed the title [WIP] Add dynamic SEO improvements and social media sharing features feat: Dynamic SEO metadata, OpenGraph images, and social sharing for articles Mar 17, 2026
Copilot AI requested a review from halilibrahimcelik March 17, 2026 00:46
@halilibrahimcelik halilibrahimcelik marked this pull request as ready for review March 17, 2026 00:47
Copilot AI review requested due to automatic review settings March 17, 2026 00:47
@halilibrahimcelik halilibrahimcelik merged commit a65eecc into development-05 Mar 17, 2026
1 of 6 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds SEO and social-sharing support to wiki article pages by generating per-article metadata (OpenGraph/Twitter/canonical), introducing a dynamic OpenGraph image route, adding in-page share actions, and sharing markdown-to-text logic via a utility.

Changes:

  • Implemented dynamic generateMetadata for /wiki/[id] with OpenGraph/Twitter/canonical fields and not-found fallbacks.
  • Added a /wiki/[id]/opengraph-image image generator and a shared stripMarkdown() helper for excerpts.
  • Added social sharing buttons (Twitter/X, Facebook, LinkedIn) plus “Copy Link” to the article viewer.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 4 comments.

File Description
lib/utils.ts Adds stripMarkdown() helper used for meta descriptions/excerpts.
components/features/wikicards/wiki-article-viewer.tsx Adds social share and copy-link UI + handlers.
app/wiki/[id]/page.tsx Adds generateMetadata() for article-specific SEO/OpenGraph/Twitter fields.
app/wiki/[id]/opengraph-image.tsx Adds an OpenGraph image generator route for article previews.
Comments suppressed due to low confidence (1)

app/wiki/[id]/page.tsx:71

  • getArticleByIdFromDB(id) is executed both in generateMetadata and again in the page component, which will result in two DB queries per request (and two cache lookups) for the same article. Consider deduping by wrapping the fetch in a shared memoized function (e.g., react's cache()), or restructuring to reuse the same promise where possible to reduce latency and load.
export async function generateMetadata({
  params,
}: ViewArticlePageProps): Promise<Metadata> {
  const { id } = await params;
  const article = await getArticleByIdFromDB(id);

  if (!article) {
    return {
      title: "Article Not Found | WikiMasters",
      description: "The article you are looking for does not exist.",
    };
  }

  const description = stripMarkdown(article.content);

  const BASE_URL =
    process.env.NEXT_PUBLIC_BASE_URL ?? "https://wikimasters.com";
  const articleUrl = `${BASE_URL}/wiki/${id}`;

  const images = article.imageUrl
    ? [{ url: article.imageUrl, width: 1200, height: 630, alt: article.title }]
    : undefined;

  return {
    title: `${article.title} | WikiMasters`,
    description,
    openGraph: {
      title: article.title,
      description,
      url: articleUrl,
      siteName: "WikiMasters",
      type: "article",
      publishedTime: article.createdAt ?? undefined,
      authors: article.authorName ? [article.authorName] : undefined,
      images,
    },
    twitter: {
      card: "summary_large_image",
      title: article.title,
      description,
      images: article.imageUrl ? [article.imageUrl] : undefined,
    },
    alternates: {
      canonical: articleUrl,
    },
  };
}

export default async function ViewArticlePage({
  params,
}: ViewArticlePageProps) {
  const { id } = await params;
  const user = await stackServerApp.getUser({ or: "redirect" });
  const userId = user.id;
  const [article, canEdit] = await Promise.all([
    getArticleByIdFromDB(id),
    authorizeUserToEditArticle(userId, id),
  ]);

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/utils.ts
Comment on lines +22 to +27
const raw = content
.replace(/[#*_`[\]()>~]/g, "")
.replace(/\n+/g, " ")
.trim()
.slice(0, maxLength);
return raw.length === maxLength ? `${raw}…` : raw;
Comment on lines +177 to +187
const handleCopyLink = async () => {
if (!navigator.clipboard) {
toast.error("Copy to clipboard is not supported in this browser.", {
position: "bottom-left",
duration: 2500,
});
return;
}
try {
await navigator.clipboard.writeText(window.location.href);
toast.success("Link copied to clipboard!", {
Comment on lines +2 to +17
import { getArticleByIdFromDB } from "@/lib/data/articles";
import { stripMarkdown } from "@/lib/utils";

export const runtime = "edge";
export const alt = "WikiMasters Article";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";

interface OgImageProps {
params: Promise<{ id: string }>;
}

export default async function ArticleOgImage({ params }: OgImageProps) {
const { id } = await params;
const article = await getArticleByIdFromDB(id);

Comment thread components/features/wikicards/wiki-article-viewer.tsx
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.

3 participants