Skip to main content

SEO & Social Metadata Reference

Overview

Complete technical reference for implementing SEO metadata, structured data (JSON-LD), and social media rich previews. For strategy and monitoring approach, see Portfolio App Security Controls section on SEO.


Scope

In scope

  • Meta tags strategy (title, description, keywords, robots)
  • Open Graph tags for social media previews
  • Twitter Card tags for X/Twitter sharing
  • JSON-LD structured data (Person, WebSite schemas)
  • Sitemap and robots.txt configuration
  • Metadata hierarchy (global vs page-level)

Out of scope

  • SEO strategy and business goals (see Planning docs)
  • Monitoring and analytics (see Operations docs)
  • Blog SEO patterns (Phase 5+)

Metadata Architecture

Layout Metadata (Global, Inherited by All Pages)

// src/app/layout.tsx
export const metadata: Metadata = {
title: {
default: 'Portfolio',
template: '%s | Portfolio',
},
description:
'Enterprise-grade full-stack portfolio: interactive CV, verified projects, and engineering evidence.',
metadataBase: SITE_URL ? new URL(SITE_URL) : undefined,
keywords: [
'full-stack engineer',
'portfolio',
'Next.js',
'TypeScript',
'enterprise architecture',
],
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
openGraph: {
type: 'website',
locale: 'en_US',
url: SITE_URL || '/',
siteName: 'Portfolio',
},
twitter: {
card: 'summary_large_image',
},
};

Page-Level Metadata (Specific to Route)

// src/app/cv/page.tsx
export const metadata: Metadata = {
title: 'CV',
description:
'Interactive curriculum vitae with verified experience and technical skills.',
};

Result: Title becomes "CV | Portfolio" (template applied)

Metadata Flow Architecture

Tri-Layer Strategy:

  1. Open Graph (Primary): Default for Facebook, LinkedIn, Discord, Slack
  2. Twitter Cards (Platform-Specific): Optimized for X/Twitter with large image format
  3. JSON-LD (Semantic): Machine-readable data for search engines and AI assistants

Why All Three?

  • Maximum platform coverage (each platform prefers specific tags)
  • Semantic understanding (JSON-LD helps search engines understand context)
  • Fallback redundancy (if Twitter tags missing, OG tags used)
  • Enhanced results (structured data enables rich snippets, knowledge panels)

Metadata Hierarchy

Order of precedence (most specific wins):

  1. Page-level metadata (page.tsx export)
  2. Layout metadata (layout.tsx export)
  3. Default fallbacks

Why this structure?

  • Define once in layout, inherit everywhere
  • Override per-page when needed
  • Single source of truth for global branding

Meta Tags Strategy

Title Tag (<title>)

Purpose: Appears in browser tab and search results (first line)

Format:

[Page Name] | Portfolio

Examples:

  • Homepage: Portfolio
  • CV: CV | Portfolio
  • Projects: Projects | Portfolio
  • Individual project: Portfolio App | Portfolio

Length: 50-60 characters (Google truncates at ~60)

Why this format?

  • Keywords first (page name appears first)
  • Brand consistency (Portfolio on every page)
  • Unique per page (no duplicate titles)

Implementation:

export const metadata: Metadata = {
title: {
default: 'Portfolio',
template: '%s | Portfolio',
},
};

// In individual pages:
export const metadata: Metadata = {
title: 'CV', // Becomes "CV | Portfolio"
};

Meta Description (<meta name="description">)

Purpose: Snippet shown in search results (150-160 characters)

Format: Action-oriented, keyword-rich, unique per page

Examples:

Homepage: "Enterprise-grade full-stack portfolio: interactive CV, verified
projects, and engineering evidence (ADRs, threat models, runbooks)."

CV: "Interactive curriculum vitae with verified experience, technical skills,
and evidence-backed accomplishments."

Projects: "Portfolio of production applications with architecture documentation,
threat models, and verified deployments."

Length: 150-160 characters (optimal for display)

Best practices:

  • Action-oriented language ("explore", "discover", "view")
  • Include target keywords naturally
  • Unique per page (no duplicates)
  • No keyword stuffing

Implementation:

export const metadata: Metadata = {
description: 'Enterprise-grade full-stack portfolio...',
};

Meta Keywords (<meta name="keywords">)

Purpose: Hint to search engines (low SEO value but doesn't hurt)

Format: Comma-separated list of target keywords

Implementation:

export const metadata: Metadata = {
keywords: [
'full-stack engineer',
'portfolio',
'Next.js',
'TypeScript',
'DevOps',
],
};

Note: Google doesn't use keywords for ranking, but some search engines do.

Robots Meta Tag

Purpose: Control indexing and following behavior

Format:

<meta
name="robots"
content="index, follow, max-image-preview:large, max-snippet:-1"
/>

Directives:

DirectiveValueMeaning
indextrueAllow search engines to index page
followtrueAllow search engines to follow links
max-image-previewlargeAllow large images in results
max-snippet-1No limit on text snippet length

Implementation:

export const metadata: Metadata = {
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-image-preview': 'large',
'max-snippet': -1,
},
},
};

Open Graph Tags

Purpose

Rich link previews on social media (Facebook, LinkedIn, Slack, Discord)

Standard Format

<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<meta property="og:url" content="https://portfolio.com/" />
<meta property="og:site_name" content="Portfolio" />
<meta property="og:title" content="Portfolio" />
<meta
property="og:description"
content="Enterprise-grade full-stack portfolio..."
/>
<meta property="og:image" content="https://portfolio.com/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />

Implementation

export const metadata: Metadata = {
openGraph: {
type: 'website',
locale: 'en_US',
url: SITE_URL || '/',
siteName: 'Portfolio',
title: 'Portfolio',
description: 'Enterprise-grade full-stack portfolio...',
images: [
{
url: '/og-image.png',
width: 1200,
height: 630,
alt: 'Portfolio - Enterprise-Grade Full-Stack Engineering',
},
],
},
};

OG Image Requirements

  • Size: 1200x630px (Facebook/LinkedIn recommended)
  • Format: PNG or JPG (PNG preferred for quality)
  • Aspect ratio: 1.91:1
  • File location: public/og-image.png
  • Alt text: Descriptive text for accessibility

Testing

Facebook Sharing Debugger LinkedIn Post Inspector Open Graph Preview


Twitter Card Tags

Purpose

Rich link previews on X/Twitter

Card Types

TypeLayoutBest for
summarySmall image left, text rightArticles
summary_large_imageLarge image above textVisual content
appMobile app install cardApps
playerVideo/audio playerMedia

We use summary_large_image for maximum visual impact

Implementation

export const metadata: Metadata = {
twitter: {
card: 'summary_large_image',
title: 'Portfolio',
description: 'Enterprise-grade full-stack portfolio...',
creator: '@yourname', // Optional: your Twitter handle
images: ['/og-image.png'],
},
};

Testing

Twitter Card Validator


JSON-LD Structured Data

Purpose

Semantic markup for search engines to understand page content

Person Schema

{
"@context": "https://schema.org",
"@type": "Person",
"name": "Your Name",
"url": "https://portfolio.com",
"description": "Enterprise-grade full-stack engineer portfolio...",
"sameAs": ["https://github.com/username", "https://linkedin.com/in/username"],
"image": "https://portfolio.com/profile-image.jpg"
}

Benefits:

  • May appear in Google Knowledge Panel
  • Helps search engines understand portfolio owner
  • Enables rich results

WebSite Schema

{
"@context": "https://schema.org",
"@type": "WebSite",
"url": "https://portfolio.com",
"name": "Portfolio",
"description": "Enterprise-grade full-stack portfolio...",
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://portfolio.com/search?q={search_term_string}"
},
"query-input": "required name=search_term_string"
}
}

Benefits:

  • Enables sitelinks search box in Google results
  • Allows users to search your site from Google
  • Improves SERP (search engine results page) presence
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{
"@type": "ListItem",
"position": 1,
"name": "Home",
"item": "https://portfolio.com/"
},
{
"@type": "ListItem",
"position": 2,
"name": "Projects",
"item": "https://portfolio.com/projects"
},
{
"@type": "ListItem",
"position": 3,
"name": "Portfolio App",
"item": "https://portfolio.com/projects/portfolio-app"
}
]
}

Implementation in React

<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "Person",
// ... schema fields
}),
}}
/>

Testing

Google Rich Results Test Schema.org Validator


Sitemap Configuration

sitemap.xml

Location: public/sitemap.xml

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://portfolio.com/</loc>
<lastmod>2026-01-28</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://portfolio.com/cv</loc>
<lastmod>2026-01-28</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://portfolio.com/projects</loc>
<lastmod>2026-01-28</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
</urlset>

Fields:

ElementFormatMeaning
locURLAbsolute page URL
lastmodYYYY-MM-DDLast modification date
changefreqEnumUpdate frequency hint
priority0.0-1.0Relative importance

Priority Guidelines:

  • 1.0 - Homepage (most important)
  • 0.9 - Key pages (Projects index)
  • 0.8 - Important pages (CV)
  • 0.7 - Secondary pages (Individual projects)
  • 0.5 - Utility pages (Contact)

robots.txt

Location: public/robots.txt

User-agent: *
Allow: /

Sitemap: https://portfolio.com/sitemap.xml

Optimization Checklist

Before deploying or submitting to search engines:

  • Titles: Unique, 50-60 characters, keywords first
  • Descriptions: Unique, 150-160 characters, no duplicate
  • OG tags: Complete on all pages (title, description, image)
  • Twitter tags: Configured with summary_large_image
  • JSON-LD: Valid schemas, tested with Rich Results Test
  • Keywords: Included naturally (no stuffing)
  • Sitemap: Generated and accessible at /sitemap.xml
  • robots.txt: Correct and accessible at /robots.txt
  • Mobile responsive: Works on small screens
  • HTTPS: All pages served securely

Monitoring

Google Search Console

Setup:

  1. Verify domain ownership
  2. Submit sitemap
  3. Monitor coverage and indexing

Track:

  • Indexed pages
  • 404 errors
  • Coverage issues
  • Search rankings
  • Click-through rate (CTR)

Tools


References