NextReady includes comprehensive SEO features to help your application rank well in search engines. This guide explains how to leverage Next.js's built-in SEO capabilities and the additional optimizations included in NextReady.
Next.js 14 provides a powerful Metadata API that NextReady leverages for SEO optimization. You can define metadata at the app level and override it for specific pages:
// src/app/layout.tsx
import { Metadata } from 'next'
export const metadata: Metadata = {
metadataBase: new URL('https://yoursite.com'),
title: {
default: 'NextReady - SaaS Starter Kit',
template: '%s | NextReady'
},
description: 'A complete SaaS starter kit built with Next.js',
keywords: ['Next.js', 'React', 'SaaS', 'Starter Kit'],
authors: [{ name: 'Your Name', url: 'https://yoursite.com' }],
creator: 'Your Company',
publisher: 'Your Company',
openGraph: {
type: 'website',
locale: 'en_US',
url: 'https://yoursite.com',
siteName: 'NextReady',
title: 'NextReady - SaaS Starter Kit',
description: 'A complete SaaS starter kit built with Next.js',
images: [
{
url: 'https://yoursite.com/og-image.jpg',
width: 1200,
height: 630,
alt: 'NextReady - SaaS Starter Kit'
}
]
},
twitter: {
card: 'summary_large_image',
title: 'NextReady - SaaS Starter Kit',
description: 'A complete SaaS starter kit built with Next.js',
creator: '@yourtwitter',
images: ['https://yoursite.com/twitter-image.jpg']
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
'max-video-preview': -1,
'max-image-preview': 'large',
'max-snippet': -1
}
}
}
Override metadata for specific pages:
// src/app/pricing/page.tsx
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Pricing',
description: 'Flexible pricing plans for your SaaS application',
openGraph: {
title: 'Pricing - NextReady',
description: 'Flexible pricing plans for your SaaS application',
url: 'https://yoursite.com/pricing'
}
}
Generate metadata dynamically based on content:
// src/app/blog/[slug]/page.tsx
import { Metadata } from 'next'
import { getPostBySlug } from '@/lib/posts'
export async function generateMetadata({
params
}: {
params: { slug: string }
}): Promise<Metadata> {
const post = await getPostBySlug(params.slug)
if (!post) {
return {
title: 'Post Not Found',
description: 'The requested post could not be found'
}
}
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
type: 'article',
publishedTime: post.date,
authors: [post.author.name],
images: [
{
url: post.coverImage,
width: 1200,
height: 630,
alt: post.title
}
]
}
}
}
NextReady automatically generates a sitemap for your application:
// src/app/sitemap.ts
import { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl = 'https://yoursite.com'
const currentDate = new Date()
// Static pages
const staticPages = [
{ path: '', priority: 1.0, changeFrequency: 'daily' },
{ path: '/pricing', priority: 0.8, changeFrequency: 'weekly' },
{ path: '/features', priority: 0.8, changeFrequency: 'weekly' },
{ path: '/contact', priority: 0.7, changeFrequency: 'monthly' },
{ path: '/blog', priority: 0.9, changeFrequency: 'daily' },
]
// Transform to sitemap format
return staticPages.map(page => ({
url: `${baseUrl}${page.path}`,
lastModified: currentDate,
changeFrequency: page.changeFrequency as 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never',
priority: page.priority,
}))
}
Add dynamic content to your sitemap:
// src/app/sitemap.ts
import { MetadataRoute } from 'next'
import { getAllPosts } from '@/lib/posts'
import { getAllProducts } from '@/lib/products'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://yoursite.com'
const currentDate = new Date()
// Static pages
const staticPages = [
// ... static pages as above
]
// Blog posts
const posts = await getAllPosts()
const blogPages = posts.map(post => ({
url: `${baseUrl}/blog/${post.slug}`,
lastModified: new Date(post.updatedAt || post.createdAt),
changeFrequency: 'weekly' as const,
priority: 0.7,
}))
// Products
const products = await getAllProducts()
const productPages = products.map(product => ({
url: `${baseUrl}/products/${product.slug}`,
lastModified: new Date(product.updatedAt),
changeFrequency: 'weekly' as const,
priority: 0.8,
}))
// Combine all pages
return [
...staticPages.map(page => ({
url: `${baseUrl}${page.path}`,
lastModified: currentDate,
changeFrequency: page.changeFrequency as 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never',
priority: page.priority,
})),
...blogPages,
...productPages,
]
}
Structured data helps search engines understand your content better. NextReady includes a component for adding JSON-LD structured data:
// src/components/schema-markup.tsx
export function SchemaMarkup({
type,
data
}: {
type: 'Organization' | 'Product' | 'Article' | 'FAQPage' | 'BreadcrumbList',
data: any
}) {
const schemaData = {
'@context': 'https://schema.org',
'@type': type,
...data
}
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schemaData) }}
/>
)
}
// Usage example for an organization
export function OrganizationSchema() {
const data = {
name: 'Your Company',
url: 'https://yoursite.com',
logo: 'https://yoursite.com/logo.png',
sameAs: [
'https://twitter.com/yourcompany',
'https://facebook.com/yourcompany',
'https://linkedin.com/company/yourcompany'
]
}
return <SchemaMarkup type="Organization" data={data} />
}
// Usage example for a product
export function ProductSchema({ product }) {
const data = {
name: product.name,
description: product.description,
image: product.image,
offers: {
'@type': 'Offer',
price: product.price,
priceCurrency: 'USD',
availability: 'https://schema.org/InStock'
}
}
return <SchemaMarkup type="Product" data={data} />
}
Add these components to your pages:
// src/app/products/[slug]/page.tsx
import { ProductSchema } from '@/components/schema-markup'
export default async function ProductPage({ params }) {
const product = await getProductBySlug(params.slug)
return (
<>
<ProductSchema product={product} />
{/* Rest of your page content */}
</>
)
}
NextReady supports internationalized SEO with next-intl. Here's how to optimize SEO for multiple languages:
// src/app/[locale]/layout.tsx
import { Metadata } from 'next'
export async function generateMetadata({
params
}: {
params: { locale: string }
}): Promise<Metadata> {
const locale = params.locale
// Load translations for metadata
const messages = await import(`@/messages/${locale}/metadata.json`)
return {
title: {
default: messages.defaultTitle,
template: messages.titleTemplate
},
description: messages.description,
openGraph: {
locale: locale === 'en' ? 'en_US' : locale === 'fr' ? 'fr_FR' : locale === 'es' ? 'es_ES' : 'de_DE',
title: messages.defaultTitle,
description: messages.description
}
}
}
Add alternate language links to help search engines understand your multilingual content:
// src/app/[locale]/layout.tsx
import { Metadata } from 'next'
export async function generateMetadata({
params
}: {
params: { locale: string }
}): Promise<Metadata> {
const locale = params.locale
const path = '/' // This would be dynamic based on the current path
// Supported locales
const locales = ['en', 'fr', 'es', 'de']
// Generate alternate links for each locale
const alternates = {
canonical: `https://yoursite.com/${locale}${path}`,
languages: {}
}
locales.forEach(l => {
alternates.languages[l] = `https://yoursite.com/${l}${path}`
})
return {
// Other metadata properties
alternates
}
}
Next.js automatically generates hreflang tags based on the alternates property in your metadata.
SEO is heavily influenced by performance. NextReady includes several optimizations for Core Web Vitals:
Use Next.js Image component for automatic image optimization:
import Image from 'next/image'
export function OptimizedImage() {
return (
<Image
src="/path/to/image.jpg"
alt="Description of the image"
width={800}
height={600}
priority={true} // For LCP images
quality={85}
placeholder="blur" // Optional blur-up
blurDataURL="data:image..." // Base64 encoded placeholder
/>
)
}
NextReady uses Next.js font optimization to eliminate layout shift:
// src/app/layout.tsx
import { Inter } from 'next/font/google'
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter'
})
export default function RootLayout({ children }) {
return (
<html lang="en" className={inter.variable}>
<body>{children}</body>
</html>
)
}
Use the Next.js Script component to optimize loading of third-party scripts:
import Script from 'next/script'
export function Analytics() {
return (
<>
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"
strategy="afterInteractive"
/>
<Script id="google-analytics" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
`}
</Script>
</>
)
}
NextReady provides a robots.txt file and robots meta tags:
// src/app/robots.ts
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/api/', '/admin/', '/private/'],
},
sitemap: 'https://yoursite.com/sitemap.xml',
}
}
Control indexing for specific pages:
// For pages that should not be indexed
export const metadata = {
robots: {
index: false,
follow: true,
}
}
// For private pages
export const metadata = {
robots: {
index: false,
follow: false,
nocache: true,
}
}
Configure different SEO settings based on environment:
// src/lib/seo.ts
export function getSeoConfig() {
const isProd = process.env.NODE_ENV === 'production'
const baseUrl = isProd
? 'https://yoursite.com'
: 'http://localhost:3000'
return {
baseUrl,
defaultTitle: 'NextReady - SaaS Starter Kit',
titleTemplate: '%s | NextReady',
robotsPolicy: isProd
? { index: true, follow: true }
: { index: false, follow: false, nocache: true }
}
}
Create a reusable SEO component for pages that don't use the App Router metadata:
// src/components/seo.tsx
import Head from 'next/head'
interface SeoProps {
title?: string
description?: string
canonical?: string
ogImage?: string
noindex?: boolean
}
export function SEO({
title,
description,
canonical,
ogImage,
noindex = false
}: SeoProps) {
const siteTitle = title
? `${title} | NextReady`
: 'NextReady - SaaS Starter Kit'
return (
<Head>
<title>{siteTitle}</title>
{description && <meta name="description" content={description} />}
{canonical && <link rel="canonical" href={canonical} />}
{/* Open Graph */}
<meta property="og:title" content={siteTitle} />
{description && <meta property="og:description" content={description} />}
{ogImage && <meta property="og:image" content={ogImage} />}
{/* Twitter Card */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={siteTitle} />
{description && <meta name="twitter:description" content={description} />}
{ogImage && <meta name="twitter:image" content={ogImage} />}
{/* Robots */}
{noindex && <meta name="robots" content="noindex,nofollow" />}
</Head>
)
}
After implementing SEO optimizations, consider these next steps: