@Moose_Said

Cairo, Egypt

@mosaid.bsky.social

🦋

- Full Time Instructor @VueSchool

- Masterclass Lead Instructor

- Full Stack developer

M

ostafa

uxt

Power

S

E

O

Unlock

What is

S

E

O

?

  • Stands for "Search Engine Optimization".

What is

S

E

O

?

  • Stands for "Search Engine Optimization".
  • Search engines are one of your site's users.

What is

S

E

O

?

  • Stands for "Search Engine Optimization".
  • They aim to improve your site's presence on the internet.
  • Search engines are one of your site's users.

What is

S

E

O

?

  • Stands for "Search Engine Optimization".
  • They aim to improve your site's presence on the internet.
  • Search engines are one of your site's users.
  • SEO refers to the actions you take to help search engines show your site to more users.

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

  • Crawlers follow specific algorithm to determine the crawl frequency and crawling speed.

S

earch

E

ngine

Crawlers

  • Crawlers visit your site and render the page similarly to how a real user would.
  • Crawlers follow specific algorithm to determine the crawl frequency and crawling speed.

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/')

SSR

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/')

CSR

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

CSR

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

CSR

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

Nuxt offers the best of both worlds

👑

Universal Rendering

S

earch

E

ngine

Crawlers

🌐

Server

Response

<!DOCTYPE html>
<html>
  <head> .. </head>

  <body> .. </body>
</html>

HTML

🔎

fetch('https://vueschool.io/')

Request

Response

Universal Rendering

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

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

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

Universal Rendering

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

Universal Rendering

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

  • Make sure you're not blocking bots.

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: /
  • Page is working and returning 200 response.

3 main things to ensure

Crawlers

can explore your site

  • Make sure you're not blocking bots.

Page is publicly accessible

No blocking meta tags, headers or Robots.txt

  • Content is indexable.
  • Page is working and returning 200 response.

Indexable file type

3 main things to ensure

Crawlers

can explore your site

  • Make sure you're not blocking bots.

Page is publicly accessible

No blocking meta tags, headers or Robots.txt

Not Spammy

  • HTML
  • JPEG | WebP | PNG.
  • XML
  • MP4 | WMV | AVI

But we can do more..

We can help search engines discover our pages

Optimize Links

  • Search engines discover new pages via anchor tags.

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>

=

  • Search engines discover new pages via anchor tags.
  • Avoid linking important pages with JavaScript and Click Event Listeners.

Optimize Links

<NuxtLink :to="{ name: 'posts', params: { slug: the-vuejs-guide } }">
  {{ post.title }}
</NuxtLink>
<div @click="navigateToPost(post.slug)">
  {{ post.title }}
</div>
  • Search engines discover new pages via anchor tags.
  • Avoid linking important pages with JavaScript and Click Event Listeners.
  • Preferably, ALL pagination links should be present in the DOM. You can either make them.

Optimize Links

Fully visible

Hidden with CSS

  • Search engines discover new pages via anchor tags.
  • Avoid linking important pages with JavaScript and Click Event Listeners.
  • Preferably, ALL pagination links should be present in the DOM.

Optimize Links

  • The linked text matters.
<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 are a big page with many links and details about the page's content.

Sitemaps

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

  • Nuxt provides a set of helpful tools for defining optimizing meta tags:
  • useHead() Composable.
  • useSeoMeta() Composable.
  • SEO components (Title, Meta, Link, Head..etc).

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

  • A web page url can have many variants.

Canonicalization

vueschool.io/courses?sortBy=Desc

vueschool.io/courses

Canonicalization

  • Canonical links is your way to tell the search engine which page has the primary content.
<head>
  <link rel="canonical" href="https://vueschool.com/courses">
</head>
  • A web page url can have many variants.

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 helps Google better understand your page's content and how it's structured.

Structured Data Markup

  • Structured data helps Google better understand your page's content and how it's structured.

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 helps Google better understand your page's content and how it's structured.

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:

  • Not essential for the initial page load.
  • Unlikely will be used by all users.

Optimize Internal Navigation

Nuxt automatically prefetch links on visibility,

which can help to reduce latency by prefetching resources before they're actually needed

Optimize Internal Navigation

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>

Optimize Internal Navigation

Preconnect & Preload Hints

Preconnect & Preload Hints

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>

Preconnect & Preload Hints

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

  • Unused CSS contributes to the overall file size of CSS files.

Reduce Unused CSS

  • Unused CSS contributes to the overall file size of CSS files.
  • Nuxt.js uses cssnano by default in the build step to minimize CSS.

Reduce Unused CSS

  • Unused CSS contributes to the overall file size of CSS files.
  • Nuxt.js uses cssnano by default in the build step to minimize CSS.
  • We can enable Nuxt PurgeCSS module to remove unused CSS.

Optimize Images

<NuxtImg
  src="/images/hero.png"
  alt="Vue School landing ..."
  sizes="400px md:1280px"
  width="1280"
  height="720"
  provider="ipx"
/>
  • Images have a huge impact on page loading speed.

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
  • Use modern image format.

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
/>
  • All images below the fold SHOULD be lazily loaded.

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
/>
  • Above the fold images SHOULD be eagerly loaded and preferably preloaded.

Optimize Images

npx nuxi module add nuxt-booster

Author: basics

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

🦋

Rank Higher & Faster: Unlock Nuxt.js SEO Power!

By Mostafa Said

Rank Higher & Faster: Unlock Nuxt.js SEO Power!

Ready to skyrocket your Nuxt.js apps to the top of search results? Join us for an eye-opening journey into the world of SEO! We'll crack the code of search engine optimization, explore Nuxt's built-in SEO superpowers, and introduce you to game-changing modules that'll give your sites an edge. Plus, we'll tackle the whole server-side vs. client-side rendering debate. Trust me, it'll be more fun than you'd expect for an SEO talk!

  • 335