MyApp

Getting Started

IntroductionInstallationPull Updates
Architecture

Setup

IDEAI AgentsMCP ServersEnvironment Variables

Workflow

Git WorkflowBuild & DeployTroubleshooting

Authentication

OverviewSetup & ConfigurationUsage & IntegrationTroubleshooting

Payments

OverviewSetup & ConfigurationUsage & IntegrationTroubleshooting

Supabase

OverviewSetup & ConfigurationTroubleshooting

Database

Database SetupPrisma ORMUsage & IntegrationMigrationsTroubleshooting

Storage

OverviewSetup & ConfigurationUsage & IntegrationTroubleshooting

Emails

OverviewSetup and ConfigurationUsage and IntegrationTroubleshooting

SEO

OverviewConfiguration & Best PracticesCustomization & Optimization

UI

OverviewSetup and ConfigurationThemingTroubleshooting
MyApp

Customization & Optimization

Customize metadata and optimize SEO for your specific needs.

This page shows how to customize Plainform's SEO features for your specific use cases.

Dynamic Metadata

Blog Posts

Generate metadata from blog post data:

app/blog/[...slug]/page.tsx
import { blogSource } from '@/lib/source';

export async function generateMetadata({ params }: { params: { slug: string[] } }) {
  const page = blogSource.getPage(params.slug);

  if (!page) {
    return { title: 'Post Not Found' };
  }

  return {
    title: page.data.title,
    description: page.data.description,
    openGraph: {
      title: page.data.title,
      description: page.data.description,
      type: 'article',
      publishedTime: page.data.date,
      images: [page.data.image],
    },
  };
}

Product Pages

Generate metadata from database:

app/products/[id]/page.tsx
import { prisma } from '@/lib/prisma/prisma';
import { siteConfig } from '@/config/siteConfig';

export async function generateMetadata({ params }: { params: { id: string } }) {
  const product = await prisma.product.findUnique({
    where: { id: params.id },
  });

  if (!product) {
    return { title: 'Product Not Found' };
  }

  return {
    title: `${product.name} | ${siteConfig.siteName}`,
    description: product.description,
    openGraph: {
      title: product.name,
      description: product.description,
      images: [product.imageUrl],
    },
  };
}

Dynamic Sitemap

Add dynamic routes to your sitemap:

app/server-sitemap.xml/route.ts
import { getServerSideSitemap } from 'next-sitemap';
import { blogSource } from '@/lib/source';
import { env } from '@/env';

export async function GET() {
  const posts = blogSource.getPages();

  const fields = posts.map((post) => ({
    loc: `${env.SITE_URL}/blog/${post.slugs.join('/')}`,
    lastmod: new Date(post.data.date).toISOString(),
    changefreq: 'weekly',
    priority: 0.7,
  }));

  return getServerSideSitemap(fields);
}

Add to next-sitemap.config.js:

next-sitemap.config.js
module.exports = {
  siteUrl: process.env.SITE_URL,
  exclude: ['/server-sitemap.xml'],
  robotsTxtOptions: {
    additionalSitemaps: [
      `${process.env.SITE_URL}/server-sitemap.xml`,
    ],
  },
};

Structured Data (JSON-LD)

Article Schema

app/blog/[...slug]/page.tsx
export default function BlogPost({ params }: { params: { slug: string[] } }) {
  const page = blogSource.getPage(params.slug);

  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: page.data.title,
    description: page.data.description,
    image: page.data.image,
    datePublished: page.data.date,
    author: {
      '@type': 'Person',
      name: page.data.author,
    },
  };

  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      <article>{/* Post content */}</article>
    </>
  );
}

Organization Schema

app/layout.tsx
import { siteConfig } from '@/config/siteConfig';
import { env } from '@/env';

export default function RootLayout({ children }) {
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Organization',
    name: siteConfig.siteName,
    url: env.NEXT_PUBLIC_SITE_URL,
    logo: `${env.NEXT_PUBLIC_SITE_URL}/logo.png`,
    sameAs: [
      'https://twitter.com/yourhandle',
      'https://github.com/yourorg',
    ],
  };

  return (
    <html>
      <body>
        <script
          type="application/ld+json"
          dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
        />
        {children}
      </body>
    </html>
  );
}

Canonical URLs

Prevent duplicate content:

Example metadata
export function generateMetadata() {
  return {
    alternates: {
      canonical: 'https://yourdomain.com/page',
    },
  };
}

Performance Optimization

Lazy Load Images

Lazy loading
<Image
  src="/image.jpg"
  alt="Description"
  width={800}
  height={600}
  loading="lazy"  // Default behavior
/>

Optimize Third-Party Scripts

app/layout.tsx
import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <Script
          src="https://analytics.example.com/script.js"
          strategy="lazyOnload"
        />
      </body>
    </html>
  );
}

Testing SEO

Local Testing

View page source (Ctrl+U) to verify:

  • <title> tag
  • <meta name="description"> tag
  • Open Graph tags

Production Testing

Use these tools after deployment:

  • Google Rich Results Test: search.google.com/test/rich-results
  • Facebook Sharing Debugger: developers.facebook.com/tools/debug
  • Lighthouse: Chrome DevTools → Lighthouse tab

Common Customizations

Custom 404 Page

app/not-found.tsx
export const metadata = {
  title: '404 - Page Not Found',
  description: 'The page you are looking for does not exist.',
};

export default function NotFound() {
  return (
    <div>
      <h1>404 - Page Not Found</h1>
    </div>
  );
}

Exclude Pages from Indexing

app/admin/page.tsx
export const metadata = {
  robots: {
    index: false,
    follow: false,
  },
};

Next Steps

  • Configuration & Best Practices - SEO configuration guide
  • Next.js Metadata API - Complete API reference
  • Schema.org - Structured data documentation

How is this guide ?

Last updated on

Configuration & Best Practices

Configure SEO settings and follow best practices for optimal search engine visibility.

Overview

Learn about Plainform's UI system built with Tailwind CSS 4 and shadcn/ui components

On this page

Dynamic Metadata
Blog Posts
Product Pages
Dynamic Sitemap
Structured Data (JSON-LD)
Article Schema
Organization Schema
Canonical URLs
Performance Optimization
Lazy Load Images
Optimize Third-Party Scripts
Testing SEO
Local Testing
Production Testing
Common Customizations
Custom 404 Page
Exclude Pages from Indexing
Next Steps