- Full Time Instructor @VueSchool
- Masterclass Lead Instructor
- Full Stack developer
ostafa
uxt
Power
S
E
O
Unlock
What is
S
E
O
?
What is
S
E
O
?
What is
S
E
O
?
What is
S
E
O
?
What is
S
E
O
?
HAPPY
S
earch
E
ngines
=
HIGH TRAFFIC
How do
S
earch
E
ngines
visit a website?
S
earch
E
ngine
Crawlers
S
earch
E
ngine
Crawlers
S
earch
E
ngine
Crawlers
S
earch
E
ngine
Crawlers
🌐
Server
fetch('https://vueschool.io/')
Request
S
earch
E
ngine
Crawlers
🌐
Server
<!DOCTYPE html>
<html>
<head> .. </head>
<body> .. </body>
</html>
HTML
fetch('https://vueschool.io/')
Request
Response
S
earch
E
ngine
Crawlers
🌐
Server
<!DOCTYPE html>
<html>
<head> .. </head>
<body> .. </body>
</html>
The returned HTML contains the actual content when rendered server side
<!DOCTYPE html>
<html>
<head> .. </head>
<body>
<main> Actual content .. </main>
</body>
</html>
HTML
fetch('https://vueschool.io/')
Request
Response
S
earch
E
ngine
Crawlers
🌐
Server
<!DOCTYPE html>
<html>
<head> .. </head>
<body> .. </body>
</html>
Or, referencing the needed JavaScript files to render the content in the browser
<!DOCTYPE html>
<html>
<head> .. </head>
<body>
<div id='app'></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
HTML
fetch('https://vueschool.io/')
Request
Response
S
earch
E
ngine
Crawlers
🌐
Server
<!DOCTYPE html>
<html>
<head> .. </head>
<body> .. </body>
</html>
<!DOCTYPE html>
<html>
<head> .. </head>
<body>
<div id='app'></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
HTML
🔎
fetch('https://vueschool.io/')
Request
Response
Or, referencing the needed JavaScript files to render the content in the browser
Watch out!
S
earch
E
ngine
Crawlers
🌐
Server
Request
<!DOCTYPE html>
<html>
<head> .. </head>
<body> .. </body>
</html>
HTML
🔎
<!DOCTYPE html>
<html>
<head> .. </head>
<body>
<div id='app'></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
Watch out!
fetch('https://vueschool.io/')
✅ Yes! SEO is possible for client-side apps.
Request
Response
S
earch
E
ngine
Crawlers
🌐
Server
Response
<!DOCTYPE html>
<html>
<head> .. </head>
<body> .. </body>
</html>
HTML
🔎
<!DOCTYPE html>
<html>
<head> .. </head>
<body>
<div id='app'></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
Watch out!
fetch('https://vueschool.io/')
⛔️ No! It's not as quick or efficient as SSR.
Request
Response
S
earch
E
ngine
Crawlers
🌐
Server
Response
<!DOCTYPE html>
<html>
<head> .. </head>
<body> .. </body>
</html>
HTML
🔎
fetch('https://vueschool.io/')
Request
Response
👑
S
earch
E
ngine
Crawlers
🌐
Server
Response
<!DOCTYPE html>
<html>
<head> .. </head>
<body> .. </body>
</html>
HTML
🔎
fetch('https://vueschool.io/')
Request
Response
Universal rendering is the default rendering mode for Nuxt
At this point, this is just typical server-side rendering
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hello, world!</title>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
Server
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hello, world!</title>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
Browser
Static HTML
Static HTML
📄
S
earch
E
ngine
Crawlers
🌐
Server
Response
<!DOCTYPE html>
<html>
<head> .. </head>
<body> .. </body>
</html>
HTML
🔎
fetch('https://vueschool.io/')
Request
Response
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hello, world!</title>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
Server
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hello, world!</title>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
Browser
Static HTML
Static HTML
📄
But, Nuxt is secretly doing its magic behind the scenes 🤫
S
earch
E
ngine
Crawlers
🌐
Server
Response
<!DOCTYPE html>
<html>
<head> .. </head>
<body> .. </body>
</html>
HTML
🔎
fetch('https://vueschool.io/')
Request
Response
To keep client-side features,
the browser loads JavaScript after the HTML is downloaded
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hello, world!</title>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
Server
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hello, world!</title>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
Browser
Static HTML
Static HTML
S
earch
E
ngine
Crawlers
🌐
Server
Response
<!DOCTYPE html>
<html>
<head> .. </head>
<body> .. </body>
</html>
HTML
🔎
fetch('https://vueschool.io/')
Request
Response
Dynamic HTML
Nuxt & Vue then takes over to enable interactivity,
achieving universal rendering
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hello, world!</title>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
Server
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Hello, world!</title>
</head>
<body>
<h1>Hello, world!</h1>
</body>
</html>
Browser
Static HTML
S
earch
E
ngine
Crawlers
🌐
Server
<!DOCTYPE html>
<html>
<head> .. </head>
<body> .. </body>
</html>
HTML
Visually Rendered Page
fetch('https://vueschool.io/')
Request
Response
https://vueschool.io
S
earch
E
ngine
Crawlers
🌐
Server
<!DOCTYPE html>
<html>
<head> .. </head>
<body> .. </body>
</html>
HTML
fetch('https://vueschool.io/')
Visually Rendered Page
Scan and Index the Content
Request
Response
https://vueschool.io
S
earch
E
ngine
Crawlers
Our goal
S
earch
E
ngine
Crawlers
Our goal
are able to
crawl your site by default
Crawlers
3 main things to ensure
Crawlers
can explore your site
Page is publicly accessible
No blocking meta tags, headers or Robots.txt
3 main things to ensure
Crawlers
can explore your site
<meta name="robots" content="noindex">
HTTP/1.1 200 OK
(...)
X-Robots-Tag: noindex
(...)
User-agent: *
Disallow: /
3 main things to ensure
Crawlers
can explore your site
Page is publicly accessible
No blocking meta tags, headers or Robots.txt
Indexable file type
3 main things to ensure
Crawlers
can explore your site
Page is publicly accessible
No blocking meta tags, headers or Robots.txt
Not Spammy
But we can do more..
We can help search engines discover our pages
Optimize Links
Optimize Links
<NuxtLink :to="{ name: 'posts', params: { slug: the-vuejs-guide } }">
{{ post.title }}
</NuxtLink>
<a href="/posts/the-vuejs-guide">
The Vue.js Guide
</a>
=
Optimize Links
<NuxtLink :to="{ name: 'posts', params: { slug: the-vuejs-guide } }">
{{ post.title }}
</NuxtLink>
<div @click="navigateToPost(post.slug)">
{{ post.title }}
</div>
Optimize Links
Fully visible
Hidden with CSS
Optimize Links
<NuxtLink to="/articles/vuejs-guide">click here</NuxtLink>
<NuxtLink to="/articles/vuejs-guide">Vue.js Guide</NuxtLink>
Optimize Links
Ensure all links are valid
npx nuxi module add link-checker
Author: harlan-zw
Sitemaps
Sitemaps
Example: https://www.foxnews.com/sitemap.xml
Sitemaps
Powerfully flexible XML Sitemaps that integrate seamlessly, for Nuxt
npx nuxi module add @nuxtjs/sitemap
Author: harlan-zw
Sitemaps
Check out the documentation for more
Robots.txt
Robots.txt
Add a robots.txt
file in the public/
directory to guide crawlers on which pages to index and which to ignore
User-agent: *
Disallow: /dashboard
Robots.txt
Manage the robots crawling your site with minimal config
npx nuxi module add robots
Author: harlan-zw
We can provide more details about our content
Page Title
Page Title
Setting a title for the page is HTML 101
<!DOCTYPE html>
<html>
<head>
<title>Nuxt Nation Rules!</title>
</head>
<body> .. </body>
</html>
Page Title
// app.vue
<script setup lang="ts">
useHead({
titleTemplate: "%s - Site Title",
});
</script>
Using the useHead()
composable is one of many ways to do it
<title>Home - Site Title</title>
// pages/index.vue
<script setup lang="ts">
useHead({
title: "Home",
});
</script>
Meta Tags
Meta Tags
Meta tags provide metadata about the web page
export default defineNuxtConfig({
app: {
head: {
charset: 'utf-8',
viewport: 'width=device-width, initial-scale=1',
}
}
})
Meta Tags
Meta tags provide metadata about the web page
Meta Tags
The useSeoMeta()
composable allows you to define your page's
meta tags as a simple object, with full TypeScript support
<script setup lang="ts">
useSeoMeta({
title: 'Nuxt Nation',
ogTitle: 'Nuxt Nation',
description: 'Boost your Nuxt skills..',
ogDescription: 'Boost your Nuxt skills..',
ogImage: 'https://example.com/image.png',
twitterCard: 'summary_large_image',
})
</script>
My favorite
Meta Tags
useSeoMeta()
composable helps you avoid typos and common mistakes, such as using name
instead of property
in some cases
<script setup>
useSeoMeta({
ogTitle: 'Nuxt Cert Bootcamp',
})
</script>
<script setup>
useHead({
meta: [
{ name: 'og:title', content: 'Nuxt Cert Bootcamp' }
]
})
</script>
<meta property=”og:title” content=”Nuxt Cert Bootcamp” />
<meta name=”og:title” content=”Nuxt Cert Bootcamp” />
Meta Tags
useSeoMeta()
composable helps you avoid typos and common mistakes, such as using name
instead of property
in some cases
Check the 'Open Graph' tab in Nuxt DevTools to spot any missing important tags
💡
<script setup>
useSeoMeta({
ogTitle: 'Nuxt Cert Bootcamp',
})
</script>
<script setup>
useHead({
meta: [
{ name: 'og:title', content: 'Nuxt Cert Bootcamp' }
]
})
</script>
<meta property=”og:title” content=”Nuxt Cert Bootcamp” />
<meta name=”og:title” content=”Nuxt Cert Bootcamp” />
Meta Tags
useSeoMeta()
composable helps you avoid typos and common mistakes, such as using name
instead of property
in some cases
It even provides ready-to-paste code snippet to fix all your missing tags!
💡
<script setup>
useSeoMeta({
ogTitle: 'Nuxt Cert Bootcamp',
})
</script>
<script setup>
useHead({
meta: [
{ name: 'og:title', content: 'Nuxt Cert Bootcamp' }
]
})
</script>
<meta property=”og:title” content=”Nuxt Cert Bootcamp” />
<meta name=”og:title” content=”Nuxt Cert Bootcamp” />
Meta Tags
Crazy good utilities for SEO and meta
npx nuxi module add nuxt-seo-utils
Author: harlan-zw
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/posts/**': {
seoMeta: {
ogDescription: 'Bla bla bla..',
ogImage: 'https://example.com'
},
},
}
})
Per route useSeoMeta()
usage straight from nuxt.config.ts
Canonicalization
Canonicalization
vueschool.io/courses?sortBy=Desc
vueschool.io/courses
Canonicalization
<head>
<link rel="canonical" href="https://vueschool.com/courses">
</head>
www or without?
Trailing slash?
Query strings?
vueschool.io/courses?sortBy=Desc
vueschool.io/courses
http or https?
Canonicalization
You can use the built-in useHead()
composable for that
// pages/courses/[slug].vue
<script setup lang="ts">
const { slug } = useRoute().params;
useHead({
link: [{ rel: "canonical", href: `https://vueschool.io/courses/${slug}` }],
});
</script>
Canonicalization
npx nuxi module add nuxt-seo-utils
Author: harlan-zw
The Nuxt SEO Utils module can auto-generate canonical links
Redirection
Redirection
It's super important for the crawler to land on the right page
// pages/posts/index.vue
<script setup lang="ts">
await navigateTo('/articles', { redirectCode: 301 })
</script>
// middleware/redirect.ts
export default defineNuxtRouteMiddleware((to, from) => {
if (to.path === '/posts') {
return navigateTo('/articles', { redirectCode: 301 })
}
})
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
"/posts": { redirect: { to: "/articles", statusCode: 301 } },
},
})
Redirection
Make sure to provide the correct status code for the redirection
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
"/posts": { redirect: { to: "/articles", statusCode: 301 } },
},
})
301
302
Permanently
moved
Temporarily
moved
Structured Data Markup
Structured Data Markup
Structured Data Markup
Structured Data Markup
<script type="application/ld+json">
{
"@context": "https://schema.org/",
"@type": "Recipe",
"name": "Party Coffee Cake",
"author": {
"@type": "Person",
"name": "Mary Stone"
},
"datePublished": "2018-03-10",
"description": "This coffee cake is awesome and perfect for parties.",
"prepTime": "PT20M"
}
</script>
Structured Data Markup
Reliable community module for managing JSON-LD
npx nuxi module add nuxt-jsonld
Author: ymmooot
<script setup lang="ts">
useJsonld(() => ({
'@context': 'https://schema.org',
'@type': 'Article',
name: `Gaming Article: ${article.title}`,
}));
</script>
Structured Data Markup
Build Schema.org graphs for Nuxt
npx nuxi module add schema-org
Author: harlan-zw
// nuxt.config.ts
export default defineNuxtConfig({
schemaOrg: {
identity: {
type: 'Organization',
name: 'Vue School',
logo: '/logo.png',
sameAs: [
'https://x.com/VueSchool_io/',
'https://www.linkedin.com/company/vueschool/',
]
}
}
})
Your page experience matters
Your page experience matters
HTTP/HTTPS?
Mobile friendly?
Interfering Ads?
Valuable content?
Core Web Vitals
How fast does it take the browser to show the final, stable visual version of the main content in the page?
Core Web Vitals
Search engines loves speed
Core Web Vitals
Core Web Vitals
Test Page Performance: https://pagespeed.web.dev/
Core Web Vitals
For deep analysis use CrUX:
Or
Use the Chrome User Experience (CrUX) report:
Server-Side Rendering
Server-Side Rendering
SSR improves the web page's initial load time by allowing content to render without waiting for JavaScript to load on the client side
Static Site Generation
Static Site Generation
Pre-render pages as static files
when they don’t need frequent updates
npx nuxi generate
Generate static pages for all discoverable links referenced on the homepage "/"
export default defineNuxtConfig({
routeRules: {
"/rss.xml": { prerender: true },
},
});
Leverage the routeRules
option in the nuxt.config
file to enable selective pre-rendering
Reduce JS Bundle Size
<script setup lang="ts">
const show = ref(false)
</script>
<template>
<LazyHeavyComponent v-if="show" />
<LazyAnotherHeavyComponent v-else />
...
</template>
Reduce JS Bundle Size
Prefix heavy components with 'Lazy' to split into
separate chunks from the final bundle
<script setup lang="ts">
const submitFeedback = (feedback: Feedback) => {
const sdk = await import('big-sdk')
await sdk.send(feedback)
}
</script>
<template>
<button @click="submitFeedback">
Submit a feedback
</button>
</template>
Reduce JS Bundle Size
Leverage dynamic imports with all dependencies that are:
Nuxt automatically prefetch links on visibility,
which can help to reduce latency by prefetching resources before they're actually needed
If too many links are visible in the viewport during the initial page load, set the prefetch-on
prop to 'interaction' instead
<template>
<NuxtLink prefetch prefetch-on="interaction">
About
</NuxtLink>
</template>
Establish early connection with external domains
<script setup lang="ts">
useHead({
link: [
{
rel: 'preconnect',
href: 'https://fonts.googleapis.com'
},
{
rel: 'stylesheet',
href: 'https://fonts.googleapis.com/css2?family=Roboto&display=swap',
crossorigin: ''
}
]
})
</script>
Or, preload and cache the entire resource
to ensure it's fully loaded upfront
<script setup lang="ts">
useHead({
link: [
{
rel: 'preload',
type: 'font/woff2',
href: '/fonts/font.woff2',
as: 'font',
crossorigin: ''
},
)}
</script>
Reduce Unused CSS
Reduce Unused CSS
Reduce Unused CSS
Reduce Unused CSS
Optimize Images
<NuxtImg
src="/images/hero.png"
alt="Vue School landing ..."
sizes="400px md:1280px"
width="1280"
height="720"
provider="ipx"
/>
Optimize Images
<img
src="/_ipx/s_2560x1440/images/hero.png"
alt="Vue School Logo"
width="1280"
height="720"
sizes="(max-width: 768px) 400px, 1280px"
srcset="
/_ipx/s_400x225/images/hero.png 400w,
/_ipx/s_800x450/images/hero.png 800w,
/_ipx/s_1280x720/images/hero.png 1280w,
/_ipx/s_2560x1440/images/hero.png 2560w
"
/>
=
HTML
<NuxtImg
src="/images/hero.png"
alt="Vue School landing ..."
sizes="400px md:1280px"
width="1280"
height="720"
provider="ipx"
format="webp"
/>
// or AVIF
Optimize Images
<NuxtImg
src="/images/hero.png"
alt="Vue School landing ..."
sizes="400px md:1280px"
width="1280"
height="720"
provider="ipx"
format="webp"
loading="lazy"
placeholder
/>
Optimize Images
<NuxtImg
src="/images/hero.png"
alt="Vue School landing ..."
sizes="400px md:1280px"
width="1280"
height="720"
provider="ipx"
format="webp"
loading="eager"
preload
/>
Optimize Images
npx nuxi module add nuxt-booster
Awesome module to hack lighthouse scores
So much more we can do!
Nuxt Nation’s lineup of amazing speakers will dive into all the best ways to optimize performance in Nuxt apps!
@Moose_Said
I'll post the slides after this
@mosaid.bsky.social
🦋