SEO Overview
Built-in SEO features in Nebutra-Sailor — Next.js App Router metadata, OG image generation, structured data, and i18n hreflang support.
What is built in
Nebutra-Sailor ships with a complete SEO foundation out of the box. You do not need to install third-party SEO libraries; everything is built on the Next.js 16 App Router metadata system.
| Feature | Status | Location |
|---|---|---|
| Static metadata | Built in | export const metadata in page files |
| Dynamic metadata | Built in | generateMetadata() in page files |
| OG image generation | Built in | @vercel/og via opengraph-image.tsx convention |
| Robots.txt | Built in | apps/landing/src/app/robots.ts |
| XML sitemap | Built in | apps/landing/src/app/sitemap.ts |
| Canonical URLs | Built in | Set via metadata.alternates.canonical |
| Structured data (JSON-LD) | Built in | Injected via <script type="application/ld+json"> in layout |
| i18n hreflang | Built in | next-intl + metadata.alternates.languages |
| Blog SEO | Built in | Sanity content with auto-generated OG images |
Next.js App Router metadata API
Next.js 16 provides two ways to export metadata from any page.tsx or layout.tsx:
Static metadata
For pages with fixed titles and descriptions, export a metadata object:
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Nebutra — AI-native SaaS Platform",
description: "Build and ship AI-powered SaaS products faster with Nebutra.",
};Dynamic metadata
For pages that depend on route parameters (e.g., a blog post slug), use generateMetadata:
import type { Metadata } from "next";
import { getPost } from "@/lib/sanity";
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string; lang: string }>;
}): Promise<Metadata> {
const { slug, lang } = await params;
const post = await getPost(slug, lang);
return {
title: post.title,
description: post.excerpt,
openGraph: {
images: [post.ogImage],
},
};
}OG image generation
Nebutra uses Vercel's @vercel/og library to generate Open Graph images at the edge. OG images are defined using the Next.js file-system convention:
apps/landing/src/app/[lang]/blog/[slug]/opengraph-image.tsxThe opengraph-image.tsx file exports an ImageResponse that renders a React component to a PNG image at request time, using the Satori renderer.
import { ImageResponse } from "next/og";
export const runtime = "edge";
export const size = { width: 1200, height: 630 };
export const contentType = "image/png";
export default async function OgImage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
// fetch post title, etc.
return new ImageResponse(
<div style={{ /* layout */ }}>
{/* content */}
</div>,
{ ...size }
);
}The runtime = "edge" export is required for @vercel/og. Without it, OG image generation runs on the Node.js runtime, which is significantly slower.
Structured data (JSON-LD)
Structured data is injected in apps/landing/src/app/[lang]/layout.tsx as a <script> tag. The root layout includes Organization and Website schemas. Individual pages (e.g., blog posts) can inject additional schemas using the same pattern.
export default function RootLayout({ children }: { children: React.ReactNode }) {
const jsonLd = {
"@context": "https://schema.org",
"@type": "Organization",
name: "Nebutra",
url: "https://nebutra.com",
};
return (
<html>
<body>
<script
type="application/ld+json"
// biome-ignore lint/security/noDangerouslySetInnerHtml: structured data is safe static JSON
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
{children}
</body>
</html>
);
}i18n SEO with hreflang
The landing page supports 7 locales via next-intl. Each page automatically generates alternates.languages metadata so search engines understand the language relationship between URLs.
export async function generateMetadata({
params,
}: {
params: Promise<{ lang: string }>;
}): Promise<Metadata> {
const { lang } = await params;
return {
alternates: {
canonical: `https://nebutra.com/${lang}`,
languages: {
"en": "https://nebutra.com/en",
"zh": "https://nebutra.com/zh",
"ja": "https://nebutra.com/ja",
// ... other locales
},
},
};
}Search engines use these hreflang annotations to serve the correct language version to users.
Blog SEO with Sanity
When FEATURE_BLOG=true, blog posts are fetched from Sanity CMS. Each post document includes:
titleandexcerptfields used for<title>and<meta name="description">- An
ogImageasset reference used for OG image<meta>tags - A
publishedAtfield used forarticle:published_time
Sanity-powered blog posts automatically include Article structured data.
Related
How is this guide?
Last updated on