SEO today is not a trick, a plugin, or a handful of keywords sprinkled across a page. It is a combination of solid structure, technical correctness, and clear content semantics.
If Google cannot clearly understand what a page is about and how its content is structured, it will not rank it seriously—regardless of how good the content itself is.
This guide covers the core SEO foundations every modern web application must have, with a practical focus on Next.js.
Server-Side Rendering Is a Requirement, Not an Option
If you want serious SEO, the content of your page must be server-side rendered.
Client-side rendered applications (pure SPAs) can be indexed, but they are:
- Slower to index
- Less reliable
- Weaker in initial ranking signals
Search engines still rely heavily on initial HTML output. If your content only appears after JavaScript execution, you are starting at a disadvantage.
Why Next.js Is a Strong SEO Foundation
Next.js is an excellent choice for SEO because it:
- Defaults to SSR and SSG patterns
- Gives you full control over metadata
- Delivers complete HTML to crawlers immediately
If your application depends on JavaScript to even display meaningful content, that is a structural SEO weakness.
Semantic HTML and Content Hierarchy
One of the most underestimated SEO fundamentals is HTML structure.
Google does not “see” your page like a human—it reads it as a document. If the document is poorly structured, search engines struggle to understand it, no matter how good the content is.
Core Rules You Must Follow
- Only one
<h1>per page - Headings must follow a logical hierarchy
- The page should be readable like an article or a lecture
Example of a Correct Heading Structure
h1
├─ h2
│ ├─ h3
│ └─ h3
├─ h2
└─ h2Skipping heading levels (h1 → h4) or using headings purely for visual styling is bad practice.
If you need a different visual appearance, use CSS—do not misuse HTML semantics.
👉 You can easily audit this using browser SEO extensions that visualize heading structures.
Metadata: Unique Title and Description for Every Page
Every route in your application must have:
- A unique
<title> - A unique
<meta description>
Practical Length Guidelines
title: 30–65 charactersdescription: 120–320 characters
Example: Global Metadata Configuration in Next.js (layout.tsx)
This example is intentionally generic and can be adapted for portfolios, blogs, or products.
import type { Metadata } from "next";
export const metadata: Metadata = {
metadataBase: new URL("https://www.example.com"),
title: "Fullstack Developer | Modern Web Applications",
description:
"Fullstack developer focused on building fast, scalable and user-friendly web applications using modern technologies.",
keywords: [
"fullstack developer",
"web development",
"Next.js",
"React",
"TypeScript",
],
openGraph: {
title: "Fullstack Developer | Modern Web Applications",
description:
"Building high-quality, performant web applications with modern tools and best practices.",
type: "website",
locale: "en_US",
url: "https://www.example.com",
siteName: "Fullstack Developer Portfolio",
images: [
{
url: "https://www.example.com/og-image.png",
width: 1200,
height: 630,
alt: "Fullstack Developer Portfolio",
},
],
},
robots: {
index: true,
follow: true,
googleBot: {
index: true,
follow: true,
"max-video-preview": -1,
"max-image-preview": "large",
"max-snippet": -1,
},
},
alternates: {
canonical: "https://www.example.com",
},
};Note: Google does not use the keywords field for ranking anymore, but keeping it does not hurt and may still be useful for other search engines or internal tooling.Dynamic Routes Require Dynamic Metadata
If your application uses routes like:
/projects/[slug]
/blog/[slug]you cannot reuse the same title and description for every page.
Next.js solves this cleanly using generateMetadata.
export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> },
parent: ResolvingMetadata
): Promise<Metadata> {
const { slug } = await params;
const item = await fetchProjectBySlug(slug);
if (!item) {
return {
title: "Content Not Found",
};
}
const previousImages = (await parent).openGraph?.images || [];
return {
title: `${item.title} | Project Overview`,
description: item.shortDescription,
openGraph: {
title: `${item.title} | Project Overview`,
description: item.shortDescription,
type: "article",
url: `https://www.example.com/projects/${slug}`,
images: item.imageUrl
? [
{
url: item.imageUrl,
width: 1200,
height: 630,
alt: item.title,
},
]
: previousImages,
},
alternates: {
canonical: `https://www.example.com/projects/${slug}`,
},
};
}This ensures every page:
- Has its own SEO identity
- Clearly communicates its topic to search engines
- Generates correct previews when shared
Links and Images: Small Details That Matter
Every:
<a>element<img>element
must have meaningful attributes.
- Links should have descriptive text or
title - Images must include
alttext (and ideallytitle)
This is not only about accessibility—Google uses these attributes to better understand context and relevance. A link that says “Click here” provides no semantic value.
Open Graph: Not Critical for Ranking, Essential for Professionalism
Open Graph metadata:
- Does not directly affect rankings
- Greatly improves link previews on social platforms
One rule you should never forget:
Open Graph images must have dimension of 1200×630
Anything else often results in cropping or degraded previews.
Redirects: Always Use 301 for Permanent Changes
If you remove or change routes:
- Use 301 redirects
- Never use 302 for permanent changes
A 301 redirect tells search engines:
“This page has permanently moved—transfer its SEO value.”
Sitemap and robots.txt Are Mandatory
Without a sitemap:
- Google can index your site
- But slower and less reliably
Next.js makes dynamic sitemap generation straightforward.
import { MetadataRoute } from "next";
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = "https://www.example.com";
const items = await fetchAllProjects();
const dynamicUrls = items.map((item: any) => ({
url: `${baseUrl}/projects/${item.slug}`,
lastModified: item.updatedAt,
changeFrequency: "monthly" as const,
priority: 0.8,
}));
return [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 1,
},
{
url: `${baseUrl}/projects`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.9,
},
...dynamicUrls,
];
}Your robots.txt must also clearly state what should and should not be indexed.
Google Search Console: The Final and Critical Step
After deployment:
- Add your site to Google Search Console
- Verify ownership (meta tag or DNS)
- Manually request indexing for key routes
- Be patient—Google needs time
Search Console is the only place where you can see:
- Actual indexing status
- Real errors
- SEO issues Google truly detects
Conclusion
Good SEO is not a hack. It is:
- Clean structure
- Technical correctness
- Consistency
If:
- Your HTML structure makes sense
- Metadata is clear and unique
- SSR is properly implemented
- Sitemap and Search Console are active
Google has everything it needs to take your site seriously.
Everything else comes later.
