We use tracking cookies to understand how you use the product and help us improve it. For more information on how we store cookies, read our  privacy policy.

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:

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

How is this guide ?

Last updated on