Docs
Features

SEO

Built-in SEO optimization — automatic meta tags, Open Graph, Twitter Cards, XML sitemap, robots.txt, JSON-LD structured data.

TikShip is built on Next.js App Router's native metadata API, giving you fine-grained SEO control on every page with zero third-party libraries.

Meta Tags

Static Metadata

// src/app/layout.tsx
export const metadata = {
  title: 'TikShip — Ship your SaaS in days',
  description: 'Production-ready Next.js SaaS boilerplate.',
  keywords: ['SaaS', 'Next.js', 'boilerplate', 'starter kit'],
}

Dynamic Metadata (per article / product)

// src/app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug)

  return {
    title: post.seoTitle || post.title,
    description: post.seoDescription || post.excerpt,
    openGraph: {
      title: post.seoTitle || post.title,
      description: post.seoDescription || post.excerpt,
      images: post.featuredImage ? [post.featuredImage] : [],
    },
  }
}

Per-article seoTitle and seoDescription fields are managed from the Content Management editor. If left blank, the post title and excerpt are used as fallbacks.

Open Graph

export const metadata = {
  openGraph: {
    title: 'TikShip',
    description: 'Ship your SaaS in days',
    url: 'https://yourdomain.com',
    siteName: 'TikShip',
    images: [
      {
        url: 'https://yourdomain.com/og-image.png',
        width: 1200,
        height: 630,
        alt: 'TikShip',
      },
    ],
    locale: 'en_US',
    type: 'website',
  },
}

Twitter Cards

export const metadata = {
  twitter: {
    card: 'summary_large_image',
    title: 'TikShip',
    description: 'Ship your SaaS in days',
    images: ['https://yourdomain.com/twitter-image.png'],
    creator: '@yourusername',
  },
}

Sitemap

Automatic XML sitemap generation. The sitemap includes all published articles and pages:

// src/app/sitemap.ts
export default async function sitemap() {
  const posts = await prisma.post.findMany({
    where: { status: 'published' },
    select: { slug: true, type: true, updatedAt: true },
  })

  const postUrls = posts.map((post) => ({
    url: `https://yourdomain.com/${post.type === 'article' ? 'blog' : 'pages'}/${post.slug}`,
    lastModified: post.updatedAt,
    changeFrequency: 'weekly' as const,
    priority: 0.8,
  }))

  return [
    { url: 'https://yourdomain.com', lastModified: new Date(), changeFrequency: 'daily' as const, priority: 1 },
    ...postUrls,
  ]
}

Access the sitemap at: https://yourdomain.com/sitemap.xml

Robots.txt

// src/app/robots.ts
export default function robots() {
  return {
    rules: [
      {
        userAgent: '*',
        allow: '/',
        disallow: ['/admin/', '/api/'],
      },
    ],
    sitemap: 'https://yourdomain.com/sitemap.xml',
  }
}

Access it at: https://yourdomain.com/robots.txt

JSON-LD Structured Data

Organization

const jsonLd = {
  '@context': 'https://schema.org',
  '@type': 'Organization',
  name: 'TikShip',
  url: 'https://yourdomain.com',
  logo: 'https://yourdomain.com/logo.png',
}

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

Article

const jsonLd = {
  '@context': 'https://schema.org',
  '@type': 'Article',
  headline: post.title,
  description: post.excerpt,
  image: post.featuredImage,
  datePublished: post.publishedAt,
  dateModified: post.updatedAt,
  author: { '@type': 'Person', name: post.author.name },
}

Product

const jsonLd = {
  '@context': 'https://schema.org',
  '@type': 'Product',
  name: product.name,
  description: product.description,
  image: product.image,
  offers: {
    '@type': 'Offer',
    price: product.price,
    priceCurrency: product.currency,
    availability: 'https://schema.org/InStock',
  },
}

Canonical URLs

Prevent duplicate-content penalties:

export const metadata = {
  alternates: {
    canonical: 'https://yourdomain.com/blog/my-article',
  },
}

See Internationalization for locale configuration.

Performance

Image Optimization

import Image from 'next/image'

<Image
  src={post.featuredImage}
  alt={post.title}
  width={1200}
  height={630}
  priority   // for above-the-fold images
/>

Best Practices

ElementGuideline
<title>Under 60 characters; unique per page; front-load keyword
Meta descriptionUnder 160 characters; include a call-to-action
H1One per page; matches primary keyword intent
Image alt textDescriptive; avoid keyword stuffing
URLsLowercase, hyphenated, no special characters
CanonicalSet on all paginated and filtered views

Monitoring

  1. Submit sitemap.xml to Google Search Console
  2. Use PageSpeed Insights for Core Web Vitals
  3. Validate structured data with Google's Rich Results Test
  4. Check mobile usability with Mobile-Friendly Test

Next Steps

SEO | Tikship