Technical SEO for Next.js Applications: Core Web Vitals and Beyond
Technical SEO implementation for Next.js — Metadata API, structured data, Core Web Vitals, sitemaps, and performance budgets.
Technical SEO determines whether search engines can discover and rank your content. Next.js App Router makes SEO cleaner, but requires deliberate implementation.
1. Metadata API
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const post = await getPost(params.slug);
return {
title: `${post.title} | Ventra Rocket Blog`,
description: post.excerpt,
alternates: {
canonical: `https://ventrarocket.vn/en/blogs/${post.slug}`,
languages: {
vi: `https://ventrarocket.vn/vi/blogs/${post.slug}`,
en: `https://ventrarocket.vn/en/blogs/${post.slug}`,
},
},
openGraph: {
type: "article",
title: post.title,
description: post.excerpt,
publishedTime: post.date,
images: [{ url: post.coverImage, width: 1200, height: 630 }],
},
};
}
2. Structured Data (JSON-LD)
function ArticleJsonLd({ post, locale }: Props) {
const schema = {
"@context": "https://schema.org",
"@type": "TechArticle",
headline: post.title,
author: { "@type": "Organization", name: "Ventra Rocket" },
datePublished: post.date,
inLanguage: locale,
keywords: post.tags.join(", "),
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}
3. Sitemap Generation
// app/sitemap.ts
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const slugs = getBlogSlugs();
const locales = ["vi", "en"];
return [
{ url: "https://ventrarocket.vn", priority: 1, changeFrequency: "daily" },
...locales.flatMap((locale) =>
slugs.map((slug) => ({
url: `https://ventrarocket.vn/${locale}/blogs/${slug}`,
lastModified: new Date(),
changeFrequency: "weekly" as const,
priority: 0.7,
}))
),
];
}
4. Core Web Vitals
LCP < 2.5s — Use priority on above-fold images:
<Image src="/hero.webp" alt="Hero" priority width={1200} height={600} />
CLS < 0.1 — Always specify image dimensions, never omit width/height.
INP < 200ms — Defer heavy work to idle time:
useEffect(() => {
const id = requestIdleCallback(() => expensiveComputation());
return () => cancelIdleCallback(id);
}, []);
5. Performance Budget in CI
// lighthouserc.js
module.exports = {
ci: {
assert: {
assertions: {
"categories:performance": ["error", { minScore: 0.85 }],
"categories:seo": ["error", { minScore: 0.95 }],
"largest-contentful-paint": ["error", { maxNumericValue: 2500 }],
"cumulative-layout-shift": ["error", { maxNumericValue: 0.1 }],
},
},
},
};
Conclusion
Technical SEO: metadata, JSON-LD, sitemaps, Core Web Vitals, performance budgets in CI. Ventra Rocket websites consistently score 90+ on PageSpeed Insights.
Related Articles
Next.js App Router: Patterns for Production
A practical guide to building fast, scalable web apps with the Next.js App Router — server components, streaming, parallel routes, intercepting routes, and caching strategies.
10 TypeScript Patterns for Cleaner Code
A practical collection of 10 TypeScript patterns — from discriminated unions and template literal types to the satisfies operator — that make your codebase more type-safe and maintainable.
React State Management in 2026: Zustand, TanStack Query, and URL State
Choosing the right state management for React — TanStack Query for server state, Zustand for global UI state, URL state for filters, useState for local state.