Skip to main content
This guide covers SEO implementation across both Next.js (@workspace/web) and React SPA (@workspace/spa) applications.

SEO Architecture

Each application type handles SEO differently:
  • @workspace/web (Next.js): Server-side metadata generation
  • @workspace/spa (React): Client-side metadata management with Unhead

Next.js SEO (@workspace/web)

Meta Tags with createMetadata

Use the createMetadata helper to generate comprehensive metadata:
import { createMetadata } from '@/core/utils/seo'

export const metadata = createMetadata({
  title: 'Home Page',
  description: 'Welcome to our application',
  image: '/images/custom-og.jpg', // Optional custom OG image
})
This generates:
  • Basic meta tags (title, description)
  • Open Graph tags for social sharing
  • Twitter Card metadata
  • Apple Web App configuration
  • Mobile-optimized tags

Generated Metadata

The createMetadata function automatically includes:
{
  title: 'Home Page | @workspace/web',
  description: 'Welcome to our application',
  applicationName: '@workspace/web',
  publisher: 'Rizeki Rifandani',
  authors: { name: 'Rizeki Rifandani', url: 'https://rifandani.com' },
  creator: 'Rizeki Rifandani',
  category: 'Personal Blog or Website',
  icons: '/favicon.ico',
  generator: 'Next.js',
  robots: {
    index: true,
    follow: true,
  },
  formatDetection: {
    telephone: true,
  },
  openGraph: {
    title: 'Home Page | @workspace/web',
    description: 'Welcome to our application',
    type: 'website',
    siteName: '@workspace/web',
    locale: 'en_US',
    images: ['/api/og?title=Home%20Page'],
    url: process.env.NEXT_PUBLIC_APP_URL,
  },
  twitter: {
    card: 'summary_large_image',
    title: 'Home Page | @workspace/web',
    description: 'Welcome to our application',
    images: ['/api/og?title=Home%20Page'],
  },
}

Structured Data (JSON-LD)

Add structured data for better search engine understanding:
import { JsonLd, createWebSite, createWebPage } from '@/core/utils/seo'

const title = 'About Us'
const description = 'Learn more about our company'
const url = process.env.NODE_ENV === 'production' 
  ? process.env.NEXT_PUBLIC_PROD_APP_URL
  : process.env.NEXT_PUBLIC_DEV_APP_URL

function AboutPage() {
  return (
    <>
      <JsonLd
        graphs={[
          createWebSite({ url, title, description }),
          createWebPage({ url, title, description }),
        ]}
      />
      {/* Page content */}
    </>
  )
}

Dynamic Open Graph Images

Generate dynamic OG images using the built-in API route:
// Automatic via createMetadata
const metadata = createMetadata({
  title: 'My Page',
  description: 'Page description',
})
// Generates: /api/og?title=My%20Page

// Custom image
const metadata = createMetadata({
  title: 'My Page',
  description: 'Page description',
  image: '/images/custom-og.jpg',
})
The OG image API supports:
  • Dynamic title rendering
  • Light/dark mode detection
  • Next.js and React logo variations
  • 1200x630px (optimal for social platforms)
Test your OG images using the OpenGraph Image Playground

React SPA SEO (@workspace/spa)

Meta Tags with useSeo

The SPA uses the useSeo hook for runtime metadata management:
import { useSeo } from '@/core/hooks/use-seo'

function HomePage() {
  useSeo({
    title: 'Home',
    description: 'Welcome to our application',
    ogImage: '/images/home-og.png', // Optional
  })

  return <div>Home Page Content</div>
}

Generated Metadata

The useSeo hook automatically generates:
{
  title: 'Home | @workspace/spa',
  description: 'Welcome to our application',
  appleMobileWebAppTitle: 'Home | @workspace/spa',
  ogTitle: 'Home | @workspace/spa',
  ogDescription: 'Welcome to our application',
  ogUrl: process.env.VITE_APP_URL,
  ogImage: '/og.png',
  twitterTitle: 'Home | @workspace/spa',
  twitterDescription: 'Welcome to our application',
  twitterSite: process.env.VITE_APP_URL,
  twitterImage: '/og.png',
}

Structured Data

The hook also creates JSON-LD structured data automatically:
// Automatically generated by useSeo
[
  defineWebSite({
    name: '@workspace/spa',
    url: process.env.VITE_APP_URL,
    title: 'Home | @workspace/spa',
    description: 'Welcome to our application',
    inLanguage: ['en-US', 'id-ID'],
  }),
  defineWebPage({
    name: '@workspace/spa',
    url: process.env.VITE_APP_URL,
    title: 'Home | @workspace/spa',
    description: 'Welcome to our application',
    inLanguage: ['en-US', 'id-ID'],
  }),
]

Sitemap Generation

Next.js (@workspace/web)

Next.js supports automatic sitemap generation:
// app/sitemap.ts
import type { MetadataRoute } from 'next'

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    {
      url: 'https://yourapp.com',
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 1,
    },
    {
      url: 'https://yourapp.com/about',
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 0.8,
    },
  ]
}
The sitemap will be automatically available at /sitemap.xml

React SPA (@workspace/spa)

For the SPA, generate the sitemap manually:
bun sitemap:gen
This runs the script in scripts/gen-sitemap and updates public/sitemap.xml.
Remember to update the sitemap generation script whenever you add new routes to your application.

Robots.txt

Both applications should include a robots.txt file:
# public/robots.txt or app/robots.txt
User-agent: *
Allow: /
Sitemap: https://yourapp.com/sitemap.xml

SEO Checklist

1

Page Metadata

Ensure every page has:
  • Unique, descriptive title (50-60 characters)
  • Compelling meta description (150-160 characters)
  • Proper Open Graph tags
  • Twitter Card metadata
2

Structured Data

Add JSON-LD structured data for:
  • WebSite schema
  • WebPage schema
  • Breadcrumbs (where applicable)
  • Article/BlogPosting (for content pages)
3

Images

Optimize social sharing images:
  • Use 1200x630px for Open Graph images
  • Include descriptive alt text
  • Compress images for fast loading
4

Sitemap & Robots

Configure crawling:
  • Generate and submit sitemap to search engines
  • Configure robots.txt properly
  • Set canonical URLs for duplicate content
5

Performance

See Performance for:
  • Core Web Vitals optimization
  • Loading performance
  • Mobile responsiveness

Testing Tools

Rich Results Test

Test your structured data for Google Search

Schema.org Validator

Validate your JSON-LD structured data

JSON-LD Playground

Test and debug JSON-LD markup

OG Image Playground

Preview and test Open Graph images

Zhead Database

Discover new meta tags for SEO and performance

Twitter Card Validator

Validate Twitter Card implementation

Best Practices

Each page should have a unique title that accurately describes the content:
  • ✅ “Product Analytics Dashboard | MyApp”
  • ❌ “Dashboard | MyApp” (too generic)
  • ❌ “MyApp” (not descriptive)
Meta descriptions should entice users to click:
  • Include primary keywords naturally
  • Clearly state the page’s value proposition
  • Keep it between 150-160 characters
  • Use active voice
Follow schema.org guidelines:
  • Use appropriate schema types
  • Include all required properties
  • Validate with Google’s Rich Results Test
  • Keep structured data up to date
Ensure your OG images and metadata look great:
  • Test on multiple platforms (Facebook, Twitter, LinkedIn)
  • Use high-quality, relevant images
  • Include brand elements consistently
  • Verify image dimensions (1200x630px)

Resources

Next.js Metadata

Official Next.js metadata documentation

Unhead

Document head management for React

Schema.org

Structured data vocabulary reference

Google Search Central

Google’s SEO and search documentation

Build docs developers (and LLMs) love