Ultimate SEO Guide for Nuxt 3
A comprehensive guide to successful technical search engine optimization with Nuxt 3. Learn topics such as HTML basics, JSON-LD, OGP and Sitemap generation.
The content shown in this post is related to Nuxt 3. If you are looking for SEO with Nuxt 2, you can check out the post Simple steps for successful SEO with Nuxt 2.
Basics
Title & Description
Setting the title and description of websites is considered one of the fundamental measures for SEO. These elements play a crucial role as they are displayed in search engine results and greatly influence a visitor's decision to click and visit your website. By carefully crafting compelling titles and descriptions, you can effectively communicate the value and relevance of your content, increasing the likelihood of attracting and engaging visitors.
To set the title and description of a page, Nuxt 3 has the Composable useHead
, which can be called inside <script setup>
of every page and component to manage your head tags.
<script setup lang="ts">
useHead({
title: 'Nuxt: The Intuitive Web Framework',
meta: [
{
name: 'description',
content: 'Build your next Vue.js application with confidence using Nuxt....'
},
],
});
</script>
Alternatively, Nuxt 3 also provides meta components such as <Title />
or <Meta />
, which can be used as an alternative to the useHead
Composable. These components operate at the template level and need to be wrapped around the <Head />
component.
<template>
<Head>
<Title>Nuxt: The Intuitive Web Framework</Title>
<Meta
name="description"
content="Build your next Vue.js application with confidence using Nuxt...."
/>
</Head>
</template>
Take a look at the list of all available meta components to learn more about them.
Moving forward, our sole focus will be on utilizing useHead
.
Update: Nuxt has come up with a new composable useSeoMeta
, which is smart way to set up fundamental SEO metadata. It helps you avoid mistakes while using useHead
. You can check out useSeoMeta, but we will continue using useHead
.
Language
It is essential to consistently indicate the language of your website. You have two options to achieve this. Firstly, if your site is exclusively in one language, you can set the language globally in the app.vue
file. Alternatively, you can continue specifying the language on a per-page basis. In both cases, the useHead
method can be employed to accomplish this task.
<script setup lang="ts">
useHead({
htmlAttrs: { lang: 'en' },
});
</script>
Canonical
The canonical URL describes where the original content of your page is located at and should be set for each page. Search engines will consider the canonical URL with increased relevance.
<script setup lang="ts">
useHead({
link: [{ rel: 'canonical', href: 'https://nuxt.com/' }],
});
</script>
Open Graph protocol (OGP)
Specifying metadata for social media plays a vital role as it enables the generation of comprehensive preview cards when sharing your pages. In this example, we will focus on implementing Facebook's Open Graph protocol (OGP) to achieve this.
The Open Graph protocol enables any web page to become a rich object in a social graph. For instance, this is used on Facebook to allow any web page to have the same functionality as any other object on Facebook.
The Open Graph protocol
test
It is recommended to provide essential metadata such as the title, type, image, and URL of the page when implementing social media preview cards. Additionally, you have the option to specify other properties such as a description or the language. For more in depth information, read further on Open Graph protocol.
<script setup lang="ts">
useHead({
meta: [
{ property: 'og:title', content: 'The Intuitive Web Framework' },
{ property: 'og:description', content: 'Build your next Vue.js application with confidence using Nuxt...' },
{ property: 'og:type', content: 'website' },
{ property: 'og:url', content: 'https://nuxt.com' },
{ property: 'og:locale', content: 'en_US' },
{ property: 'og:image', content: 'https://nuxt.com/social.jpg' },
});
</script>
These details will result in a preview with title, description and image when shared on social media, as shown in the image below.
Structured data (JSON-LD)
JSON-LD is a widely adopted format for creating structured data. It provides a standardized way to organize and present data on your website. For instance, you can utilize JSON-LD to store FAQ elements as structured data. When implemented correctly, these FAQ elements can appear in search results as a distinct questions and answers section, enhancing the visibility and usability of your content.
Setup
We will use the Nuxt module nuxt-jsonld. First, install the dependency.
npm install nuxt-jsonld
Now add it to your modules inside your Nuxt configuration.
export default defineNuxtConfig({
modules: ['nuxt-jsonld'],
});
Implementation
Now we have the composable useJsonld()
available within <script setup>
, allowing us to specify any structured data. In the following we will add a single FAQ element to the page as structured data.
<script setup lang="ts">
useJsonld({
'@context': 'https://schema.org',
'@type': 'FAQPage',
mainEntity: [
{
'@type': 'Question',
name: 'Lorem ipsum dolor sit amet?',
acceptedAnswer: {
'@type': 'Answer',
text: 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr.',
},
},
],
});
</script>
You can use the playground of json-ld.org to try out some examples. Also, you can test your structured data at any time with Test Rich Results. On Schma.org, you can see all the different types and parameters that are available to use.
HTML
Headline hierarchy
Search engines try to understand your website. It is advisable to establish a well-structured heading structure. For example, each page should only have one <h1>
element. The headings should be logically structured according to the content.
But you don't have to check the structure manually in your HTML. There are tools for that. If you use Google Chrome, I can highly recommend META SEO inspector.
Links
When creating links, it is important to include both the link text and the relation attribute, such as rel="external"
. Search engines rely on this information to better understand the purpose and nature of the links. If you have a link that only displays an icon, you can still include a hidden text using CSS. It is essential to provide this information for all links on your website.
For a comprehensive list of possible rel
attribute values and their meanings, you can refer to the MDN article on this topic. The article will provide you with valuable insights into the available options for specifying the relationship between your links and the linked resources.
<template>
<!-- INTERNAL LINK -->
<NuxtLink to="/" rel="next">
<Logo />
<!-- TEXT HIDDEN WITH CSS -->
<span class="sr-only">Homepage</span>
</NuxtLink>
<!-- EXTERNAL LINK -->
<NuxtLink to="https://nuxt.com/" rel="external">
Nuxt
</NuxtLink>
</template>
Images
When it comes to images, similar principles apply as with links, but with the usage of different attributes. It is essential to consider the following guidelines:
Alternative Text: Each image should have descriptive alternative text that conveys the meaning of the image. The
alt
attribute is used for this purpose and is crucial for accessibility and search engine understanding.Loading: The
loading
attribute specifies when an image should load. For images located far outside the visible area, you can use the value "lazy." This means the image will only load as it approaches the viewport. If you want the image to load immediately, you can specify the value "eager."Width and Height: Always include the
width
andheight
attributes for images to avoid layout shifts (Cumulative Layout Shift - CLS). When the dimensions are specified, it helps the browser allocate the necessary space for the image, preventing unexpected layout changes. Search engines, including Google, consider a good user experience and stable layout as important ranking factors.Title: The
title
attribute can provide additional context and information about the image. It serves both accessibility purposes and aids search engines in understanding the image's content.
<template>
<img
src="https://placeholder.pics/svg/640x480"
alt="Placeholder"
loading="lazy"
width="640"
height="480"
title="Placeholder"
/>
</template>
We will soon be covering the topic of optimized images with Nuxt Image. Stay tuned!
Favicon
Your website should also show your logo in the browser tab. For this purpose, there is a really handy tool called realfavicongenerator. You can upload your logo, and it will help you convert it to favicons. After that, you can download the favicons and paste them into your /public
directory.
The only thing left for you to do is to include the favicons globally. For this, we will use app.vue
in this example to set the favicon globally.
This is how it could look like:
<script setup lang="ts">
useHead({
link: [
{ rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' },
{ rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' },
],
});
</script>
Sitemap
Every website should have a sitemap. The sitemap allows your website to be indexed by search engines. It includes all the subpages of the website, but also can include pages of particular importance, as well as language versions of a page. Mostly, the sitemap is a file called sitemap.xml
and is placed in the root directory of your webspace. For example, you can check this website's sitemap here: https://thenextbit.de/sitemap.xml.
Given that the community module @nuxt/sitemap hasn't made the transition to Nuxt 3 yet, you can consider creating the sitemap yourself. In the following, I will show you two ways to accomplish this.
Custom Sitemap Module for SSG
If you are new to working with custom modules in Nuxt 3, I highly recommend referring to the comprehensive Module Author Guide to gain a deeper understanding.
The purpose of this module is to seamlessly integrate into the build process and automatically generate a sitemap that dynamically contains the pages from your CMS. This is made possible by leveraging the nitro:build:public-assets
hook, which conveniently triggers just before the SSG's build process begins.
To get started, we need to install the xml dependency. It is a sophisticated JavaScript-based XML generator that will aid us in creating the sitemap in the correct xml-format.
npm install xml -D
In this example, we will name the module sitemap.module.ts
and place it directly within the ~/modules
folder.
Within this file, retrieve your page data from the appropriate source. Then, configure the response parameters to match the structure required by your API.
import { writeFileSync } from 'fs';
import { defineNuxtModule } from '@nuxt/kit';
import xml from 'xml';
export default defineNuxtModule({
meta: {
name: 'sitemap',
configKey: 'sitemap',
},
defaults: {
declaration: '<?xml version="1.0" encoding="UTF-8"?>',
xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9',
},
setup(options, nuxt) {
nuxt.hook('nitro:build:public-assets', (nitroConfig) => {
// SKIP ON DEVELOPMENT
if (nitroConfig.options.dev) return;
// GET YOUR PAGES FROM YOUR DATA SOURCE
const pages = [
{
loc: 'https://nuxt.com/',
lastmod: '2023-11-01',
priority: 1.0,
changefreq: 'monthly',
},
];
// ADJUST ACCORDING TO YOUR DATA SOURCE
const sitemapItems = pages.map(({ loc, lastmod, priority, changefreq }) => {
return {
url: [{ loc }, { lastmod }, { priority }, { changefreq }],
};
});
// CREATE SITEMAP
const sitemap =
options.declaration +
xml({
urlset: [{ _attr: { xmlns: options.xmlns } }, ...sitemapItems],
});
// WRITE SITEMAP
writeFileSync(nuxt.options.rootDir + '/.output/public/sitemap.xml', sitemap);
});
},
});
In order for the module to be picked up by Nuxt, we still need to add it to the Nuxt configuration.
export default defineNuxtConfig({
modules: [
'~/modules/sitemap.module',
],
});
Now, the sitemap module is fully prepared and operational, ready when generating your site with npm run generate
.
To further optimize this module, consider implementing additional enhancements. For instance, you can output the relevant steps in the console. This will provide clarity and transparency, allowing you to easily track and comprehend the progression of each step while generating your site.
Take this as a starting template and customize it for your individual needs.
Sitemap with Server Routes
In this section, I'll demonstrate how to create a server-side generated sitemap that will be created on request. Similar to the example on creating a custom sitemap module for SSG above, we’ll use the xml
dependency for this setup.
npm install xml -D
Start by setting up a server route in ~/server/routes/sitemap.xml.ts
. Within this file, retrieve your page data from the appropriate source. Then, configure the response parameters to match the structure required by your API.
import xml from 'xml';
export default defineEventHandler((event) => {
// GET YOUR PAGES FROM YOUR DATA SOURCE
const pages = [
{ loc: 'https://nuxt.com/', lastmod: '2023-11-01', priority: 1.0, changefreq: 'monthly' },
];
// CREATE SITEMAP ITEMS
const sitemapItems = pages.map((page) => {
return {
url: [
{ loc: page.loc },
{ lastmod: page.lastmod },
{ priority: page.priority },
{ changefreq: page.changefreq },
],
};
});
// CREATE SITEMAP
const sitemap =
'<?xml version="1.0" encoding="UTF-8"?>' +
xml({
urlset: [
{ _attr: { xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9' } },
...sitemapItems,
],
});
// SET RESPONSE HEADER
setHeader(event, 'Content-Type', 'text/xml');
// RETURN SITEMAP
return sitemap;
});
Once set up, your sitemap will be accessible at /sitemap.xml
on your domain. Use this as a foundation and tailor it to suit your specific needs.
Feedback
Thanks for reading! If you have any feedback, please feel free to email us to info@thenextbit.de. We would be happy to hear from you.
Recommendations
- SEO
- Nuxt
- Aug 5, 2021
Simple steps for successful SEO with Nuxt 2
In this post I explain how to successfully implement SEO aspects like OGP, JSON-LD and good HTML structure technically in Nuxt.js.
- Nuxt
- Mixins
- SEO
- Oct 18, 2021
Advanced SEO-Workflow with Nuxt 2
Learn how to improve your Nuxt SEO-Workflow using Mixins and the "Don’t repeat yourself" principle.