Summary and General Principles
On this page
Summary and General Principles
SEO (Search Engine Optimization) is a set of techniques that help search engines understand and rank your website better. SEO optimization includes on-page SEO and technical SEO, as well as performance optimization and user experience.
In the next sections, we'll go through each aspect in detail: HTML/meta structure, structured data (JSON-LD), robots/sitemap, performance, and accessibility.
graph LR
A[Robots.txt & Sitemap] -->|Determine| B[Allowed and prioritized URLs]
B --> C[Fetch page]
C --> D{Static page or JS-rendered}
D --> E[Generate content (SSR/CSR rendering)]
E --> F[Add to index (Indexing)]
F --> G[User searches and views results]
- where robots.txt and sitemap.xml guide the crawler on which URLs are allowed to be crawled, after which Google fetches the page, processes the HTML/JS, and then indexes the content.
On-Page and Content Optimization
-
Title tag and Meta description: Every page needs a unique, concise, on-topic
<title>tag and<meta name="description">. Google uses these tags to display in search results.html<title>Article title - Site name</title> <meta name="description" content="A concise description of the page content, summarizing the main idea and containing the primary keyword">Don't stuff keywords; the description should be compelling to increase click-through rate. Lighthouse will flag a page that's missing a title or description tag.
-
Robots tag: Use
<meta name="robots">to control crawling/indexing. By default, Google treats a page asindex, follow. If you need to prevent indexing, usenoindex.html<meta name="robots" content="noindex,nofollow">If you need to configure this via an HTTP header, you can use
X-Robots-Tag. Google supportsrobotsin the HTTP header, which is useful for non-HTML files. -
Canonical and Hreflang: Use
<link rel="canonical" href="URL">to mark the source URL among similar pages (e.g., paginated versions, print versions, URLs with parameters). Canonical helps normalize URLs and avoid duplicate content.html<link rel="alternate" href="https://example.com/vi" hreflang="vi"> <link rel="alternate" href="https://example.com/en" hreflang="en"> -
Structured data (JSON-LD): Apply Schema.org so Google can understand specific content types (Article, Product, Recipe, Event, Book, Breadcrumb, etc.). JSON-LD is recommended because it's easy to implement and doesn't interfere with the HTML.
html<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "Article", "headline": "Article title", "datePublished": "2026-05-01", "author": { "@type": "Person", "name": "Author name" }, "publisher": { "@type": "Organization", "name": "Organization name", "logo": { "@type": "ImageObject", "url": "https://example.com/logo.png" } }, "image": ["https://example.com/img1.jpg","https://example.com/img2.jpg"], "description": "A short description of the article content" } </script>According to Google, adding structured data helps make search results richer (rich results) and can increase CTR. However, you must follow schema.org's rules strictly for Google to recognize it.
-
Basic and Semantic HTML tags: Use
<h1>...<h6>correctly for titles and content hierarchy; don't skip h1. Tags like<nav>,<header>,<main>,<article>,<footer>help crawlers understand the page structure.jsximport Head from 'next/head'; <Head> <title>Page title</title> <meta name="description" content="Page description"/> </Head>In Nuxt, use
head()on the page ornuxt.config.jsto set meta. -
Images and media: Every image should have a short, concise
altdescribing the image content, useful for users who can't see the image and for SEO.html<img src="mens-shirt.webp" alt="Blue men's shirt" loading="lazy">Lazy-loading (the
loading="lazy"attribute) for off-screen images improves speed, but should be paired with a fallback if the page is JS-heavy (since Googlebot may skip lazy-loaded content without JS).
Technical SEO
-
robots.txt: The robots.txt file in the root directory tells bots which URLs they're allowed to access. Example:
plaintextUser-Agent: * Allow: / Disallow: /private/ Sitemap: https://example.com/sitemap.xmlPut
Sitemap:in robots.txt so bots can automatically discover the sitemap. In Next.js 13+, you can createapp/robots.txtorapp/robots.tsto generate it automatically.ts// app/robots.ts import type { MetadataRoute } from 'next'; export default function robots(): MetadataRoute.Robots { return { rules: { userAgent: '*', allow: '/', disallow: '/private/' }, sitemap: 'https://example.com/sitemap.xml', } }Robots rules can target specific bots: for example,
User-agent: Googlebotto customize behavior specifically for Google. Note: robots.txt only controls crawling; if a URL has already been indexed via another link, robots.txt won't prevent it from appearing in search results. -
Sitemap.xml: Create a sitemap (XML) listing all important URLs along with metadata (lastmod, changefreq, priority). It should be placed at the root (e.g.,
/sitemap.xml) and declared in robots.txt.xml<?xml version="1.0" encoding="UTF-8"?> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <url> <loc>https://example.com/</loc> <lastmod>2026-05-08</lastmod> <changefreq>weekly</changefreq> <priority>1.0</priority> </url> <url> <loc>https://example.com/about</loc> <lastmod>2026-05-01</lastmod> <changefreq>monthly</changefreq> <priority>0.8</priority> </url> <!-- add other URLs --> </urlset>With Next.js and the app router, you can use
app/sitemap.tsexporting a function that returns an array of URLs, and Next automatically generates the XML. Next.js example:ts// app/sitemap.ts (Next 13) import type { MetadataRoute } from 'next'; export default function sitemap(): MetadataRoute.Sitemap { return [ { url: 'https://example.com/', lastModified: new Date(), changeFrequency: 'weekly', priority: 1 }, { url: 'https://example.com/about', lastModified: new Date(), changeFrequency: 'monthly', priority: 0.8 }, // ... ]; }The output is a
sitemap.xmlcontaining a<urlset>similar to the one above. Besides URLs, sitemaps also support declaring<image>and<video>entries for images/videos if needed. -
Redirects: Use 301 redirects when changing a URL or moving a page to pass link equity (link juice) to the new URL. Avoid long redirect chains.
htaccessRewriteEngine On RewriteRule ^(.*)/$ /$1 [R=301,L]Or Node/Express:
jsapp.get('/old-page', (req, res) => { res.redirect(301, '/new-page'); });This tells Google that the old URL has permanently moved to the new URL. Also, avoid soft 404s (returning 200 for an error page) by making sure error pages return a 404/410 status code.
-
HTTP Headers: Set headers correctly (Content-Type, UTF-8 encoding). Use the
Cache-Controlheader to specify caching: for example, for static assets whose filenames contain a hash, useCache-Control: max-age=31536000, immutable; for dynamic HTML, useCache-Control: max-age=0, must-revalidate. -
Server speed: Make sure response time is fast (low Time to First Byte). You can use HTTP/2 or HTTP/3 (QUIC) on the server to reduce connection latency and allow multiplexing.
Performance and UX Optimization
Website performance indirectly affects SEO through user experience (Core Web Vitals). The following techniques are important:
-
HTTP/2 or HTTP/3: These newer protocols let multiple requests share a single TLS connection, reducing latency compared to HTTP/1.1. If your server or CDN supports it, enable it.
-
CDN (Content Delivery Network): Bring static content (images, CSS, JS) closer to users, reducing load time. For example, using Cloudflare, AWS CloudFront, etc.
-
Cache-Control: Set a reasonable
Cache-Controlheader for resources. For example, CSS/JS/images with hashed filenames can usemax-age=31536000, immutable, while dynamic HTML needsno-cacheor a shortmax-age. -
Critical CSS and Non-blocking CSS: Keep only the critical CSS (for the above-the-fold content) in
<head>and load it directly; the rest of the CSS can be loaded asynchronously. -
Preconnect, Preload, Prefetch (Resource Hints): Use
<link rel="preconnect">to pre-establish connections to important domains (e.g., Google Fonts, Analytics).html<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preload" href="/fonts/myfont.woff2" as="font" type="font/woff2" crossorigin>These techniques significantly reduce LCP time when used properly. Note: don't overuse preload (it can waste bandwidth).
-
Image compression and optimization: Reduce image size using WebP/AVIF or compression (JPEG/PNG). Use the
<picture>tag with different sources for responsive images. Example:html<picture> <source type="image/webp" srcset="image-1.webp 1x, image-2.webp 2x"> <source type="image/jpeg" srcset="image-1.jpg 1x, image-2.jpg 2x"> <img src="image-1.jpg" alt="Image description"> </picture>Also, lazy loading, mentioned above, helps reduce load when there are many images.
-
Bundle splitting and Code splitting: For large JS applications (React/Vue), split code into smaller chunks for faster page loads. Next.js automatically splits page-level chunks, for example. Vue/Nuxt support dynamic imports.
-
Check Core Web Vitals (LCP, FID, CLS): Use Lighthouse (DevTools or CI) or PageSpeed Insights to measure.
-
Off-screen Lazy Loading (Lazy hydration): For SPAs, only render JavaScript client-side when truly necessary. However, if you lazy-load important above-the-fold content, make sure bots can still access it via static HTML.
The comparison table below summarizes some performance techniques/resources:
| Technique | Purpose / Application | Benefits | Drawbacks / Difficulties |
|---|---|---|---|
| HTTP/2 / HTTP/3 | Upgraded transport protocol | Reduces latency, multiple concurrent connections | Requires server HTTPS support |
| CDN | Global resource distribution | Reduces geographic latency, handles more load | Cost, complex cache management |
| Preconnect / DNS-prefetch | Pre-establish connections & DNS early | Reduces DNS/TLS delay for important domains | Need to know which services are used heavily upfront |
| Preload / Pre-fetch | Prioritize loading important resources | Improves LCP (images, fonts, CSS) | Wastes bandwidth if overused |
| Bundle Splitting | Split JS/CSS into smaller chunks | Reduces initial load, faster page speed | Complex configuration, requires cache management |
| Lazy Loading | Load off-screen resources on demand | Reduces initial load resources | Needs fallback content for SEO |
| Critical CSS inlining | Inline critical CSS directly | Removes initial render-blocking CSS | Increases page size, harder to maintain |
| Cache-Control Header | Controls browser caching | Reduces repeated requests, speeds up reload | Needs cache-busting when resources change |
Accessibility and UX
SEO isn't purely mechanical: user experience (UX) and accessibility both indirectly affect SEO. Google favors mobile-friendly pages with high accessibility.
-
Alt and ARIA tags: As mentioned,
altis important for images. Also use ARIA attributes/semantic roles when needed (e.g., addrole="navigation"for a navbar,aria-labelfor icon-only buttons). -
Headings: Use only one
<h1>per page (the main title); use<h2>,<h3>... for sub-sections in order. Heading tags help Google understand the topic and content structure. -
Responsive and mobile-friendly: Add
<meta name="viewport" content="width=device-width, initial-scale=1">so Google recognizes the page as mobile-friendly. Mobile-first indexing is Google's default. -
UX and interaction: Fast load speed and smooth interaction are key factors in retaining users. Make sure buttons/links are large enough to tap easily (minimum 48x48px).
Framework / CMS-specific guides
React / Next.js
-
Server-side Rendering (SSR) & Static Generation (SSG): Next.js supports both SSR (returns HTML rendered from the server) and SSG (built at deploy time). For good SEO, prefer SSR/SSG over pure CSR.
jsximport Head from 'next/head'; export default function Page() { return ( <> <Head> <title>Page title</title> <meta name="description" content="Page description"/> <link rel="canonical" href="https://example.com/page" /> </Head> <h1>Hello SEO</h1> {/* ... */} </> ); }(Next.js 13 App Router lets you create
app/page.tsxalong withexport const metadata = { title: ..., description: ..., alternates: { canonical: '...' }}.) -
Robots.txt & Sitemap: Next.js 13 has built-in support for robots/sitemap. Add
app/robots.txtorapp/robots.tsto generate robots. Addapp/sitemap.xmlorapp/sitemap.tsto generate the sitemap. -
Image Optimization: Use Next.js's
<Image>component (since Next 10) to automatically compress images and lazy-load them. Set up srcset resolutions. Example:jsximport Image from 'next/image'; <Image src="/cat.jpg" width={800} height={600} alt="Cute cat"/> -
Head handling: Use
next/headto make sure meta loads in head; use thekeyprop if it repeats. Per the docs,<meta name="robots">is a required directive and<link rel="canonical">should be present on every page.
Vue / Nuxt.js
-
Universal Mode (SSR): Always run Nuxt in
universal(SSR) mode rather than SPA mode, since SSR pre-renders content so bots can fully index it. For example, innuxt.config.js:mode: 'universal'. -
Meta Tags: Nuxt 2 uses
head()on a component/page ornuxt.config.jsto definetitle,meta,link,script. Example on a page:jsexport default { head() { return { title: 'About us', meta: [ { name: 'description', content: 'About us page description' } ], link: [ { rel: 'canonical', href: 'https://example.com/about' } ] } } }Nuxt 3 (with the Composition API) can use
useHead()or a similarnuxt.config.tsconfiguration. -
Sitemap/robots: Use the @nuxtjs/sitemap module to auto-generate a sitemap (e.g., install with:
yarn add @nuxtjs/sitemapand add it tomodulesin the config). -
Lazy Loading: Use
nuxt/imageto optimize images or theloading="lazy"attribute. Nuxt also supports dynamic imports for components (code splitting).
Node.js / Express (Server-rendered)
-
SSR via Templates: With Express, if you use a view engine (EJS, Pug...), you return complete HTML. EJS example:
html<title><%= title %></title> <meta name="description" content="<%= description %>">In the router:
jsapp.get('/', (req, res) => { res.render('index', { title: 'Home', description: 'Home page description' }); });Make sure to place meta tags in the shared layout. If you're not using a template, you can use server-side React (Next.js) or Vue SSR with Node.
-
Sitemap/robots: With Express, simply place a
robots.txtfile in the public directory, or create an endpoint yourself, like:jsapp.get('/robots.txt', (req, res) => { res.type('text/plain'); res.send('User-agent: *\nDisallow: /admin/'); });Same for
/sitemap.xml. You can use the express-sitemap-xml package to automate it.
PHP (Server-rendered, e.g. WordPress)
-
PHP Templates: Insert meta tags in the PHP theme. Example in header.php:
php<title><?php echo esc_html(get_the_title()); ?></title> <meta name="description" content="<?php echo esc_attr(get_the_excerpt()); ?>">Many CMSs (WordPress, Joomla) have SEO plugins (Yoast, All in One SEO) to automatically add meta tags and a sitemap. WordPress 5.5+ auto-generates a
wp-sitemap.xmlsitemap. -
Sitemap/robots: WordPress dynamically generates robots.txt (accessible at
/robots.txt). Make sure it containsSitemap: https://example.com/sitemap_index.xml. Or use a plugin to customize it.
Automated Testing and CI
-
Lighthouse CI & Audit Tools: Use Google's Lighthouse CI in your pipeline to run performance, accessibility, and SEO checks on every commit or PR.
-
Unit/Integration Tests: You can write tests with Jest, Mocha, or Cypress/Playwright to assert that pages have a
<title>,<meta name="description">, canonical tag, and correct heading structure. -
Lint/CI SEO: Apply SEO-focused eslint plugins (such as [eslint-plugin-jsx-a11y] for checking ARIA) and check for broken links and a valid sitemap. Run
npm run auditor CI scripts to validate.
Deployment and Monitoring
-
Search Console / Analytics: Register the site in Google Search Console to monitor index status (Coverage) and search performance (impressions, CTR per keyword). Google Analytics 4 provides user behavior data.
-
Log & Error Monitoring: Check server logs to see whether Googlebot is being blocked (HTTP 200 for bots). Use a tool like Loggly or Sentry to detect server errors (5xx errors).
-
Alerts and Dashboards: Use PagerDuty/Sentry to alert on page errors, or set up a Core Web Vitals dashboard (CrUX data) in Search Console. Track Lighthouse/benchmarks over time.
-
Google Site Verification meta tag: To verify Search Console, add a
<meta name="google-site-verification" content="...">meta tag to the homepage, or use DNS verification.
Checklist and Rollout Roadmap
SEO Rollout Checklist for a small/medium project:
- Environment setup: Connect the site to Google Search Console & Analytics. Ensure HTTPS. Identify the key URLs to optimize (homepage, categories, main articles).
- On-page: Write titles, descriptions, and quality content for every page. Make sure
<h1>contains the primary keyword, with clear subheadings. - Important HTML tags: Add
<meta name="robots">,<meta charset>,<meta name="viewport">. Add<link rel="canonical">for similar pages. - Structured data: Implement JSON-LD (Article, Breadcrumb, Organization, etc.) for content. Test with the Rich Results Test.
- Robots & Sitemap: Create robots.txt and sitemap.xml (with the URLs that need indexing). Submit to Search Console. Configure in your framework (e.g., Next.js
app/robots.ts,app/sitemap.ts). - Basic performance: Make sure the page loads well on mobile (responsive,
<meta viewport>). Optimize images, minify CSS/JS, use HTTP/2. Use Lighthouse to fix basic issues. - Accessibility: Check alt tags on images, form labels, sufficient color contrast. Use Lighthouse's Accessibility audit category to improve.
- Monitoring: Set up Search Console crawling, Core Web Vitals. Set up a periodic Lighthouse CI job.
- After launch: Monitor Coverage/Search Analytics reports. Analyze traffic (keywords, CTR). Improve based on the data.
Priority roadmap (example):
- Month 1: Audit current SEO; fix robots issues, add a sitemap, set up Search Console; fix URL structure/canonical if needed.
- Month 2: Add missing meta tags, JSON-LD; improve article content; optimize images.
- Month 3: Optimize performance (set up CDN, HTTP/2, preload); fix Cumulative Layout Shifts; improve mobile UX.
- Month 4: Integrate CI (Lighthouse CI, SEO unit tests); set up monitoring; start a backlink/off-page campaign if needed.
Gantt chart illustrating the roadmap:
Technology & tool comparison tables
1. Optimization techniques vs Pros/Cons:
| Technique/Tool | Purpose / Application | Benefits | Limitations / Difficulties |
|---|---|---|---|
| Meta tags (title, desc) | Optimize the search result snippet | Increases CTR, controls content introduction | Needs quality writing, no duplication |
| Canonical | Normalize URLs | Prevents duplicate content, consolidates ranking authority | Forgetting to use it causes duplicate content |
| Structured Data (JSON-LD) | Rich results (rich snippets) | Increases CTR, gives search priority information | Requires strict rule compliance, extra debugging |
| robots.txt | Controls crawling | Prevents bots from crawling unimportant pages | A syntax error can block bots from the entire site |
| Sitemap.xml | Helps bots discover URLs | Ensures Google knows all URLs that need indexing | Sitemap management gets complex on large sites |
| Next.js / Nuxt modules | Auto-generate robots/sitemap | Convenient, built-in integration | Must be configured correctly to avoid config errors |
| Lighthouse CLI | Quality checking | Automated audits (Perf, SEO, A11y) | Lab results need to be cross-checked with real-world data |
| Google Search Console | Monitor pages (index, CrUX) | Reports crawl errors, page metrics, security | Requires regular checking and interaction |
| SEO Audit Tools | Overall SEO analysis | Detects SEO issues, suggests improvements | Many tools require a paid subscription |
2. Framework/Hosting:
| Platform | SSR/SSG | Built-in SEO support | Example of adding a Meta tag |
|---|---|---|---|
| Next.js | SSR/SSG | next/head, app/metadata, next-sitemap, next-robots | <Head><title>...</title><meta name="description" content="..."/></Head> |
| Nuxt (Vue) | SSR/SSG | head() on page/component, sitemap/robots modules | export default { head() { return { title: '...', meta: [ { name: 'description', content: '...' } ] } } } |
| Node/Express | SSR | manual (EJS, Pug) | <title><%= title %></title><meta name="description" content="<%= desc %>"> |
| PHP (WordPress) | SSR | Plugins (Yoast), WP Sitemap XML (wp-sitemap.xml) | <?php bloginfo('name'); ?> in the title, Yoast automatically adds meta tags |
The tables above illustrate the differences in tools and usage: for example, Next.js 13 supports auto-generated robots.ts/sitemap.ts files, while plain Node requires manual coding; Nuxt has a convenient sitemap module; WordPress has integrated plugins.
robots.txt, sitemap, and JSON-LD examples
robots.txt (example):
User-Agent: *
Allow: /
Disallow: /private/
Host: example.com
Sitemap: https://example.com/sitemap.xml
sitemap.xml (example):
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
<lastmod>2026-05-08</lastmod>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://example.com/about</loc>
<lastmod>2026-05-01</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
</urlset>
JSON-LD (Article example):
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Introduction to SEO in web development",
"datePublished": "2026-05-08",
"author": { "@type": "Person", "name": "John Doe" },
"publisher": { "@type": "Organization", "name": "VietWebCorp",
"logo": { "@type": "ImageObject", "url": "https://example.com/logo.png" } },
"image": ["https://example.com/images/seo-guide.jpg"],
"description": "A detailed guide to on-page, technical, and off-page SEO techniques for web development."
}
</script>
Conclusion
This is an in-depth guide for developers applying SEO: from basic HTML tag configuration to performance, structured data, and site architecture. Follow the checklist step by step, verify with Lighthouse and Search Console, and continuously optimize based on real-world data.
Main references: Google's SEO guides (SEO Starter Guide, Meta tags docs), Google Search Central documentation; SEO guides for Next.js, Nuxt, and other frameworks.
