Documentation

Search Engine Optimization

Learn how to optimize your Dirstarter website for search engines and social media sharing.

Dirstarter comes with built-in SEO features to help your directory website rank better in search engines and look great when shared on social media.

Metadata Configuration

The metadata configuration in Dirstarter is centralized and type-safe using Next.js's Metadata API. The base configuration is located in config/metadata.ts:

config/metadata.ts
import type { Metadata } from "next"
import { linksConfig } from "~/config/links"
import { siteConfig } from "~/config/site"
 
export const metadataConfig: Metadata = {
  openGraph: {
    url: "/",
    siteName: siteConfig.name,
    locale: "en_US",
    type: "website",
    images: { url: `${siteConfig.url}/opengraph.png`, width: 1200, height: 630 },
  },
  twitter: {
    site: "@dirstarter",
    creator: "@piotrkulpinski",
    card: "summary_large_image",
  },
  alternates: {
    canonical: "/",
    types: { "application/rss+xml": linksConfig.feed },
  },
}

Static Pages

For static pages, you can define metadata directly in the page component:

app/(web)/about/page.tsx
export const metadata: Metadata = {
  title: "About Us",
  description: "Dirstarter is a complete Next.js solution for building profitable directory websites.",
  openGraph: { ...metadataConfig.openGraph, url: "/about" },
  alternates: { ...metadataConfig.alternates, canonical: "/about" },
}

Dynamic Pages

For dynamic pages (like tool pages, blog posts), use the generateMetadata function:

app/(web)/[slug]/page.tsx
export const generateMetadata = async (props: PageProps): Promise<Metadata> => {
  const tool = await getTool(props)
  const url = `/${tool.slug}`
 
  return {
    ...getMetadata(tool),
    alternates: { ...metadataConfig.alternates, canonical: url },
    openGraph: { url, type: "website" },
  }
}

OpenGraph Images

Dirstarter automatically generates OpenGraph images for tools using Next.js's Image Response API. The images are generated on-demand and optimized for social media sharing.

Base Template

The base OpenGraph image template is defined in components/web/og/og-base.tsx:

components/web/og/og-base.tsx
export const OgBase = ({ faviconUrl, name, description, children }: OgBaseProps) => {
  return (
    <div style={{ /* styles */ }}>
      <div style={{ /* styles */ }}>
        <div>
          {faviconUrl && (
            <img
              src={faviconUrl}
              alt=""
              width={64}
              height={64}
              style={{ borderRadius: "0.5rem" }}
            />
          )}
          {name}
        </div>
        <p>{getExcerpt(description, 125)}</p>
        <div>
          <LogoSymbol />
          <span>{config.site.name}{config.site.tagline}</span>
        </div>
      </div>
    </div>
  )
}

Dynamic Generation

OpenGraph images are generated dynamically for each tool using the opengraph-image.tsx route:

app/(web)/[slug]/opengraph-image.tsx
export default async function Image({ params }: PageProps) {
  const { slug } = await params
  const tool = await findTool({ where: { slug } })
 
  return new ImageResponse(
    <OgBase 
      name={tool.name} 
      description={tool.description} 
      faviconUrl={tool.faviconUrl} 
    />,
    {
      width: 1200,
      height: 630,
      fonts: [
        {
          name: "Geist",
          data: await loadGoogleFont("Geist", 400),
          weight: 400,
          style: "normal",
        },
        {
          name: "GeistBold",
          data: await loadGoogleFont("Geist", 600),
          weight: 600,
          style: "normal",
        },
      ],
    }
  )
}

Sitemap Generation

Dirstarter automatically generates a sitemap for all your pages using Next.js's built-in sitemap generation. The sitemap is configured in app/sitemap.ts:

app/sitemap.ts
export default async function Sitemap(): Promise<MetadataRoute.Sitemap> {
  const [tools, categories, tags] = await Promise.all([
    findToolSlugs({}),
    findCategorySlugs({}),
    findTagSlugs({}),
  ])
 
  const pages = ["/about", "/advertise", "/submit"]
  const now = new Date()
 
  return [
    // Home
    createEntry("", now, { changeFrequency: "daily", priority: 1 }),
 
    // Static pages
    ...pages.map(p => createEntry(p, now, { changeFrequency: "monthly" })),
 
    // Posts
    createEntry("/blog", now),
    ...posts.map(p => createEntry(`/blog/${p._meta.path}`, new Date(p.updatedAt ?? p.publishedAt))),
 
    // Tools
    ...tools.map(t => createEntry(`/${t.slug}`, t.updatedAt)),
 
    // Categories
    createEntry("/categories", now),
    ...categories.map(c => createEntry(`/categories/${c.slug}`, c.updatedAt)),
 
    // Tags
    createEntry("/tags", now),
    ...tags.map(t => createEntry(`/tags/${t.slug}`, t.updatedAt)),
  ]
}

Robots.txt

The robots.txt file is also automatically generated and configured to allow search engines to crawl your site while protecting sensitive routes:

app/robots.ts
export default function Robots(): MetadataRoute.Robots {
  const { url } = config.site
 
  return {
    rules: {
      userAgent: "*",
      allow: "/",
      disallow: ["/admin/*", "/auth/*"],
    },
    host: url,
    sitemap: `${url}/sitemap.xml`,
  }
}

Additional SEO Features

  1. Canonical URLs: Every page has a canonical URL to prevent duplicate content issues.
  2. RSS Feed: Built-in RSS feed support for blog posts and content updates.
  3. Structured Data: Automatically generated JSON-LD for tools and blog posts.
  4. Mobile Optimization: All pages are responsive and mobile-friendly by default.
  5. Performance: Built-in image optimization and lazy loading for better Core Web Vitals.
Edit on GitHub

Last updated on

On this page