Skip to content
intermediate Phase 17 · Performance & Security

SEO & Accessibility

Implement SEO best practices, meta tags, structured data, and WCAG accessibility standards.

1h
0 problems
Topic Progress 0%

Meta Tags and Open Graph

Meta Tags and Open Graph

Essential Meta Tags for SEO

<head>
  <title>React Performance Optimization Guide | MyDevBlog</title>
  <meta name="description" content="Learn how to optimize React apps with memoization, code splitting, and lazy loading. Step-by-step guide with real-world examples.">
  <meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large">
  <meta name="author" content="Jane Developer">
  <link rel="canonical" href="https://mydevblog.com/posts/react-performance">

  <!-- Open Graph for Facebook, LinkedIn -->
  <meta property="og:type" content="article">
  <meta property="og:title" content="React Performance Optimization Guide">
  <meta property="og:description" content="Step-by-step guide to optimize React apps with memoization and code splitting.">
  <meta property="og:image" content="https://mydevblog.com/images/react-perf-og.jpg">
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="630">
  <meta property="og:url" content="https://mydevblog.com/posts/react-performance">
  <meta property="og:site_name" content="MyDevBlog">
  <meta property="article:published_time" content="2025-01-15T08:00:00Z">
  <meta property="article:author" content="Jane Developer">
  <meta property="article:tag" content="React">
  <meta property="article:tag" content="Performance">

  <!-- Twitter Card -->
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:site" content="@mydevblog">
  <meta name="twitter:creator" content="@janedev">
  <meta name="twitter:title" content="React Performance Optimization Guide">
  <meta name="twitter:description" content="Step-by-step guide to optimize React apps.">
  <meta name="twitter:image" content="https://mydevblog.com/images/react-perf-twitter.jpg">
</head>

Dynamic Meta Tags in React with react-helmet-async

import { Helmet } from 'react-helmet-async';

function BlogPost({ post }) {
  const structuredData = {
    '@context': 'https://schema.org',
    '@type': 'BlogPosting',
    headline: post.title,
    image: post.coverImage,
    datePublished: post.publishedAt,
    dateModified: post.updatedAt,
    author: { '@type': 'Person', name: post.author },
  };

  return (
    <>
      <Helmet>
        <title>{post.title} | MyDevBlog</title>
        <meta name="description" content={post.excerpt.slice(0, 160)} />
        <meta property="og:type" content="article" />
        <meta property="og:title" content={post.title} />
        <meta property="og:description" content={post.excerpt.slice(0, 200)} />
        <meta property="og:image" content={post.coverImage} />
        <meta property="og:url" content={`https://mydevblog.com/posts/${post.slug}`} />
        <link rel="canonical" href={`https://mydevblog.com/posts/${post.slug}`} />
        <script type="application/ld+json">
          {JSON.stringify(structuredData)}
        </script>
      </Helmet>
      <article>
        <h1>{post.title}</h1>
        <time dateTime={post.publishedAt}>
          {new Date(post.publishedAt).toLocaleDateString('en-US', {
            year: 'numeric', month: 'long', day: 'numeric',
          })}
        </time>
        <div dangerouslySetInnerHTML={{ __html: post.content }} />
      </article>
    </>
  );
}

Astro Component for Meta Tags

---
interface Props {
  title: string;
  description: string;
  image?: string;
  canonical?: string;
}

const { title, description, image, canonical } = Astro.props;
const siteTitle = `${title} | MyDevBlog`;
const ogImage = image || '/default-og.jpg';
const url = canonical || Astro.url.href;
---

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>{siteTitle}</title>
  <meta name="description" content={description.slice(0, 160)} />
  <meta name="robots" content="index, follow" />
  <link rel="canonical" href={url} />
  <meta property="og:title" content={siteTitle} />
  <meta property="og:description" content={description.slice(0, 200)} />
  <meta property="og:image" content={ogImage} />
  <meta property="og:url" content={url} />
  <meta property="og:type" content="website" />
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content={siteTitle} />
  <meta name="twitter:description" content={description.slice(0, 200)} />
  <meta name="twitter:image" content={ogImage} />
</head>

Structured Data and Schema.org

Structured Data and Schema.org

JSON-LD for Articles and Blog Posts

// components/ArticleSchema.ts
interface ArticleProps {
  title: string;
  description: string;
  author: string;
  publishedAt: string;
  updatedAt: string;
  image: string;
  url: string;
}

export function generateArticleSchema(article: ArticleProps) {
  return {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: article.title,
    description: article.description,
    image: article.image,
    datePublished: article.publishedAt,
    dateModified: article.updatedAt,
    author: {
      '@type': 'Person',
      name: article.author,
      url: 'https://mydevblog.com/authors/jane',
    },
    publisher: {
      '@type': 'Organization',
      name: 'MyDevBlog',
      logo: {
        '@type': 'ImageObject',
        url: 'https://mydevblog.com/logo.png',
      },
    },
    mainEntityOfPage: {
      '@type': 'WebPage',
      '@id': article.url,
    },
  };
}

FAQ Schema for Rich Snippets

// lib/faq-schema.ts
interface FAQItem {
  question: string;
  answer: string;
}

export function generateFAQSchema(faqs: FAQItem[]) {
  return {
    '@context': 'https://schema.org',
    '@type': 'FAQPage',
    mainEntity: faqs.map(faq => ({
      '@type': 'Question',
      name: faq.question,
      acceptedAnswer: {
        '@type': 'Answer',
        text: faq.answer,
      },
    })),
  };
}

// Usage in Astro page
---
const faqs = [
  { question: 'What is React?', answer: 'A JavaScript library for building user interfaces.' },
  { question: 'Why use hooks?', answer: 'Hooks let you use state and lifecycle in functional components.' },
];
const faqSchema = generateFAQSchema(faqs);
---

<script type="application/ld+json" set:html={JSON.stringify(faqSchema)} />
```\n
### Product Schema for E-commerce

```javascript
// schemas/product.ts
export function generateProductSchema(product) {
  return {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.name,
    image: product.images,
    description: product.description,
    sku: product.sku,
    brand: { '@type': 'Brand', name: product.brand },
    offers: {
      '@type': 'Offer',
      url: `https://mystore.com/products/${product.slug}`,
      priceCurrency: 'USD',
      price: product.price,
      availability: product.inStock
        ? 'https://schema.org/InStock'
        : 'https://schema.org/OutOfStock',
      itemCondition: 'https://schema.org/NewCondition',
    },
    aggregateRating: product.rating
      ? {
          '@type': 'AggregateRating',
          ratingValue: product.rating.average,
          reviewCount: product.rating.count,
        }
      : undefined,
  };
}

BreadcrumbList Schema

<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    { "@type": "ListItem", "position": 1, "name": "Home", "item": "https://mydevblog.com" },
    { "@type": "ListItem", "position": 2, "name": "React", "item": "https://mydevblog.com/category/react" },
    { "@type": "ListItem", "position": 3, "name": "Performance Guide", "item": "https://mydevblog.com/posts/react-performance" }
  ]
}
</script>

Validate Structured Data

# Test with Google Rich Results Test
# https://search.google.com/test/rich-results

# Or use schema.org validator
# https://validator.schema.org/

# Programmatic validation
npx schema-dts-gen --validate src/schemas/

Sitemaps and Robots.txt

Sitemaps and Robots.txt

robots.txt Configuration

# robots.txt - Control crawler access
User-agent: *
Allow: /
Disallow: /api/
Disallow: /admin/
Disallow: /dashboard/
Disallow: /private/
Disallow: /search?*
Disallow: /*.json$

# Block AI crawlers if desired
User-agent: GPTBot
Disallow: /

User-agent: ChatGPT-User
Disallow: /

# Sitemap location
Sitemap: https://mydevblog.com/sitemap.xml
Sitemap: https://mydevblog.com/sitemap-index.xml

Dynamic Sitemap Generation

// scripts/generate-sitemap.ts
import { writeFileSync } from 'fs';
import { db } from '../src/lib/database.js';

const BASE_URL = 'https://mydevblog.com';

interface SitemapURL {
  loc: string;
  lastmod?: string;
  changefreq: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
  priority: string;
}

async function generateSitemap(): Promise<void> {
  const posts = await db.query(
    `SELECT slug, updated_at FROM posts WHERE published = true ORDER BY updated_at DESC`
  );

  const categories = await db.query(
    `SELECT slug, updated_at FROM categories ORDER BY updated_at DESC`
  );

  const staticPages: SitemapURL[] = [
    { loc: BASE_URL, changefreq: 'daily', priority: '1.0', lastmod: new Date().toISOString() },
    { loc: `${BASE_URL}/about`, changefreq: 'monthly', priority: '0.8' },
    { loc: `${BASE_URL}/blog`, changefreq: 'daily', priority: '0.9' },
    { loc: `${BASE_URL}/contact`, changefreq: 'yearly', priority: '0.5' },
  ];

  const postUrls: SitemapURL[] = posts.rows.map(post => ({
    loc: `${BASE_URL}/posts/${post.slug}`,
    lastmod: post.updated_at.toISOString(),
    changefreq: 'monthly',
    priority: '0.7',
  }));

  const categoryUrls: SitemapURL[] = categories.rows.map(cat => ({
    loc: `${BASE_URL}/category/${cat.slug}`,
    lastmod: cat.updated_at.toISOString(),
    changefreq: 'weekly',
    priority: '0.6',
  }));

  const allUrls = [...staticPages, ...postUrls, ...categoryUrls];

  const xml = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${allUrls.map(url => `  <url>
    <loc>${url.loc}</loc>
    ${url.lastmod ? `<lastmod>${url.lastmod}</lastmod>` : ''}
    <changefreq>${url.changefreq}</changefreq>
    <priority>${url.priority}</priority>
  </url>`).join('\n')}
</urlset>`;

  writeFileSync('public/sitemap.xml', xml);
  console.log(`Sitemap generated with ${allUrls.length} URLs`);
}

generateSitemap();

Sitemap Index for Large Sites

<!-- sitemap-index.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <sitemap>
    <loc>https://mydevblog.com/sitemap-posts.xml</loc>
    <lastmod>2025-01-15T00:00:00Z</lastmod>
  </sitemap>
  <sitemap>
    <loc>https://mydevblog.com/sitemap-categories.xml</loc>
    <lastmod>2025-01-14T00:00:00Z</lastmod>
  </sitemap>
  <sitemap>
    <loc>https://mydevblog.com/sitemap-pages.xml</loc>
    <lastmod>2025-01-10T00:00:00Z</lastmod>
  </sitemap>
</sitemapindex>

Canonical URLs and Pagination

<!-- Canonical URL to prevent duplicate content -->
<link rel="canonical" href="https://mydevblog.com/posts/react-performance">

<!-- Pagination signals -->
<link rel="prev" href="https://mydevblog.com/blog?page=2">
<link rel="next" href="https://mydevblog.com/blog?page=4">

<!-- Hreflang for multi-language -->
<link rel="alternate" hreflang="en" href="https://mydevblog.com/en/posts/react-performance">
<link rel="alternate" hreflang="es" href="https://mydevblog.com/es/posts/react-performance">
<link rel="alternate" hreflang="x-default" href="https://mydevblog.com/posts/react-performance">

Submit Sitemap to Search Engines

# Google Search Console
# Visit: https://search.google.com/search-console
# Add sitemap URL: https://mydevblog.com/sitemap.xml

# Bing Webmaster Tools
# Visit: https://www.bing.com/webmasters
# Submit: https://mydevblog.com/sitemap.xml

# Programmatic ping
curl "https://www.google.com/ping?sitemap=https://mydevblog.com/sitemap.xml"
curl "https://www.bing.com/indexnow" -d '{"host": "mydevblog.com", "urlList": ["https://mydevblog.com/posts/react-performance"]}'

Accessibility and WCAG Standards

Accessibility and WCAG Standards

Semantic HTML for Screen Readers and SEO

<!-- BAD: non-semantic -->
<div class="header">
  <div class="nav">
    <div class="nav-item" onclick="goHome()">Home</div>
  </div>
</div>
<div class="main">
  <div class="post-title">My Article</div>
  <div class="post-content">Content here</div>
</div>

<!-- GOOD: semantic HTML -->
<header>
  <nav aria-label="Main navigation">
    <a href="/">Home</a>
  </nav>
</header>
<main>
  <article>
    <h1>My Article</h1>
    <p>Content here</p>
  </article>
</main>

ARIA Labels and Landmarks

// Accessible navigation component
function Navigation() {
  return (
    <nav aria-label="Main navigation" role="navigation">
      <ul role="menubar">
        <li role="none">
          <a role="menuitem" href="/" aria-current="page">Home</a>
        </li>
        <li role="none">
          <a role="menuitem" href="/blog">Blog</a>
        </li>
        <li role="none">
          <a role="menuitem" href="/about">About</a>
        </li>
      </ul>
    </nav>
  );
}

// Accessible form with error handling
function ContactForm() {
  const [error, setError] = useState(null);

  return (
    <form aria-labelledby="form-title" noValidate>
      <h2 id="form-title">Contact Us</h2>
      {error && (
        <div role="alert" aria-live="assertive" className="error">
          {error}
        </div>
      )}
      <div>
        <label htmlFor="email">Email address</label>
        <input
          id="email"
          type="email"
          aria-required="true"
          aria-invalid={!!error}
          aria-describedby={error ? 'email-error' : undefined}
        />
        {error && <span id="email-error" className="error-text">{error}</span>}
      </div>
      <button type="submit">Send</button>
    </form>
  );
}

Image Accessibility

<!-- Descriptive alt text for meaningful images -->
<img src="chart.png" alt="Bar chart showing 40% increase in React adoption from 2023 to 2025">

<!-- Empty alt for decorative images -->
<img src="decorative-border.svg" alt="" role="presentation">

<!-- Complex images with long description -->
<figure>
  <img src="architecture.png" alt="System architecture diagram" aria-describedby="arch-desc">
  <figcaption id="arch-desc">
    The system uses a three-tier architecture: React frontend communicates with
    Express API which connects to PostgreSQL database and Redis cache.
  </figcaption>
</figure>

Keyboard Navigation

// Focus management for modal dialogs
function Modal({ isOpen, onClose, title, children }) {
  const closeButtonRef = useRef(null);
  const previousFocusRef = useRef(null);

  useEffect(() => {
    if (isOpen) {
      previousFocusRef.current = document.activeElement;
      closeButtonRef.current?.focus();
    }
    return () => {
      previousFocusRef.current?.focus();
    };
  }, [isOpen]);

  useEffect(() => {
    function handleEscape(e) {
      if (e.key === 'Escape') onClose();
    }
    if (isOpen) {
      document.addEventListener('keydown', handleEscape);
      return () => document.removeEventListener('keydown', handleEscape);
    }
  }, [isOpen, onClose]);

  if (!isOpen) return null;

  return (
    <div role="dialog" aria-modal="true" aria-labelledby="modal-title">
      <div className="overlay" onClick={onClose} />
      <div className="modal-content">
        <h2 id="modal-title">{title}</h2>
        {children}
        <button ref={closeButtonRef} onClick={onClose} aria-label="Close dialog">
          ×
        </button>
      </div>
    </div>
  );
}

Color Contrast and WCAG Compliance

/* WCAG AA requires 4.5:1 contrast ratio for normal text */
/* WCAG AAA requires 7:1 contrast ratio for normal text */

/* Pass: good contrast */
.text-primary {
  color: #1a1a2e;  /* contrast 15.4:1 on white */
}

.text-secondary {
  color: #555555;  /* contrast 7.5:1 on white */
}

/* Fail: poor contrast */
.text-bad {
  color: #aaaaaa;  /* contrast 2.3:1 on white - FAILS */
}

/* Focus indicators - required for keyboard navigation */
:focus-visible {
  outline: 2px solid #005fcc;
  outline-offset: 2px;
}

/* Skip to main content link */
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: #005fcc;
  color: white;
  padding: 8px;
  z-index: 100;
}

.skip-link:focus {
  top: 0;
}

Automated Accessibility Testing

# Install axe-core for automated testing
npm install axe-core @axe-core/react @axe-core/playwright

# React integration
import axe from '@axe-core/react';
if (process.env.NODE_ENV !== 'production') {
  axe(React, ReactDOM, 1000);
}

# Playwright integration
test('page has no accessibility violations', async ({ page }) => {
  await page.goto('/');
  const results = await new AxeBuilder({ page }).analyze();
  expect(results.violations).toEqual([]);
});

# CLI audit
npx pa11y https://mydevblog.com
npx lighthouse https://mydevblog.com --only-categories=accessibility

Quiz

1. What is the recommended length for a meta description tag?

Question 1 options

2. Which structured data format does Google recommend for SEO?

Question 2 options

3. What does the canonical URL tag help prevent?

Question 3 options

Flashcards

Question

What are the three main social media meta tag sets for SEO?

Answer

Open Graph (Facebook/LinkedIn) uses og:title, og:description, og:image, og:url. Twitter Card uses twitter:card, twitter:title, twitter:description, twitter:image. Standard meta uses name="description", name="robots", and canonical link tags. Each platform reads its own tags to generate rich previews when content is shared.

Question

What is the purpose of JSON-LD structured data in SEO?

Answer

JSON-LD (JavaScript Object Notation for Linked Data) provides search engines with explicit information about page content in a machine-readable format. It enables rich snippets in search results like star ratings, FAQ dropdowns, product prices, and event details. Schema.org vocabulary defines types like Article, Product, FAQPage, and BreadcrumbList that help Google understand and display content appropriately.

Question

How does WCAG accessibility compliance improve SEO rankings?

Answer

WCAG standards improve SEO because search engines use similar criteria to evaluate pages. Semantic HTML (proper heading hierarchy, alt text, ARIA labels) helps crawlers understand content structure. Keyboard navigation ensures all content is accessible to crawlers. Color contrast and readable fonts improve user engagement metrics. Captioned videos and descriptive link text provide additional context signals that search engines use for ranking.

Revision Notes

Key Takeaways

  • 1. Always include unique title tags (50-60 chars) and meta descriptions (120-160 chars) on every page
  • 2. Use JSON-LD for structured data as it is Google's recommended format and easier to maintain
  • 3. Generate XML sitemaps dynamically and submit them to Google Search Console and Bing Webmaster Tools
  • 4. Implement Open Graph and Twitter Card tags to control how content appears when shared on social media
  • 5. Semantic HTML and WCAG accessibility standards directly impact both user experience and search rankings
  • 6. Use canonical URLs to prevent duplicate content issues across paginated, filtered, or multi-language pages

Interview Tips

  • Explain the difference between on-page SEO (meta tags, content, structure) and off-page SEO (backlinks, social signals)
  • Describe how you would audit a site's SEO: check meta tags, structured data validation, sitemap submission, Core Web Vitals, and accessibility
  • Know the Google Search Console features: URL Inspection, Performance reports, Coverage errors, Sitemap submission
  • Discuss why server-side rendering (SSR) or static site generation (SSG) improves SEO compared to client-side rendering
  • Explain structured data types: Article, Product, FAQ, BreadcrumbList, and how they produce rich snippets

Cheat Sheet

SEO Quick Reference: Title 50-60 chars | Meta description 120-160 chars | OG image 1200x630px | Twitter card summary_large_image | JSON-LD preferred over Microdata | Canonical URL prevents duplicates | Sitemap: max 50,000 URLs / 50MB | robots.txt: User-agent, Allow, Disallow, Sitemap | WCAG AA: 4.5:1 contrast ratio | Alt text required for all meaningful images