There are 5.6 billion Google Searches a day
63,000 queries a second (avg.)
But how high is the percentage of websites that actually
receive traffic from Google?
Guess!
Not even 10 percent!
SEO in a Vue.js World ๐
VueConf US 2020
About me
Alexander Lichter
Nuxt.js Core Team Member
@TheAlexLichter
What is SEO?
Search Engine Optimization
-
It's not rocket science!
-
Users first - Search engines second
-
Continuous work needed
-
Easy to get started with - Hard to master
-
Like web development, it is changing a lot
Search Engine Optimization
-
Important for pages search engines can crawl
-
Marketing pages, company & business sites
-
Forums, Help databases & FAQs
-
Blogs / Articles of any kind
-
-
Not relevant for
-
Content behind any kind of Authentication
-
Short-lived content
-
Three pillars of SEO
On-page
Off-page
Technical
Link building
...
Content
Keywords
UX
Meta tags
...
Social media
Citations
Authority
Page speed
...
Broken links
Security
Sitemap
We focus on two of them
On-page
Technical
SEO and Vue.js
- Vue.js SPAs generate HTML through JS
Is JavaScript a problem for Search Engines?
Yes, for some...
- No content when JS is disabled or not loaded
...but mostly eastern one's
Can Google fetch and
index Vue.js SPAs?
Yes, but...
Yes
-
...meta tags aren't present, no preview when sharing links
-
...mistake in your JS could lead to indexing blank pages
-
...delayed content might not get indexed all
-
...indexing doesn't mean a page ranks well
-
...fragment router mode is bad for SEO
-
...extra work for multi-page applications
Can Google fetch and
index Vue.js SPAs?
TL;DR
...using "just" the SPA mode isn't ideal for SEO. It can work but solely relying on it is not enough
Yes, but...
What else if not SPA?
Server Side Rendering
...on the fly
...at build time (JAMstack)
PS: You can also run your own setup for both, but it'll increase your owrk
Nuxt.js
Nuxt.js
Vuepress
Gridsome
Before we start with SEO
Set up your page in the Google Search Console
Before we start with SEO
Add an analysis tool to your website, e.g. Matomo or Google Analytics
Before we start with SEO
Bonus: Use a paid tool like Moz, Semrush or ahrefs to analyze your competition
Let's improve our SEO game
-
We will use Nuxt.js for most of the code examples shown
-
Can be used for both, dynamic SSR and JAMstack
-
Comes with vue-meta out of the box
-
-
vue-meta will make our SEO efforts enjoyable ๐๐ป
-
Examples can be easily applied to other frameworks, even to custom SSR setups
-
Examples will become more and more specific
-
Last but not least, users first!
What is vue-meta?
A Vue library to manage HTML metadata
<template>
...
</template>
<script>
export default {
metaInfo: {
title: 'My Example App',
titleTemplate: '%s - Yay!',
htmlAttrs: {
lang: 'en'
}
}
}
</script>
Plain Vue
<template>
...
</template>
<script>
export default {
head() {
return {
title: 'My Example App',
titleTemplate: '%s - Yay!',
htmlAttrs: {
lang: 'en'
}
}
}
}
</script>
Nuxt.js
Let's get it on!
SEO Category
Effort:ย ๐ถ
๐ถ ย ย ย ย ย ย ย ย ย is low
ย
๐ถ๐ถ๐ถ๐ถ๐ถ ย ย is very high
Basic security
Technical
Effort:ย ๐ถ
- Enable HTTPS if you haven't done that already
- Set security headers like NOSNIFF
- Keep your CMS/staff accounts/... in good shape
Mobile Friendliness
Technical
Effort:ย ๐ถ - ๐ถ๐ถ๐ถ๐ถ๐ถ
- A must have nowadays
- A lot of traffic comes from mobile devices
- Ranking factor
- Effort depends on current state
Text Compression
Technical
Effort:ย ๐ถ
- Also a must have for every page out there
- GZIP comes usually by default
- Brotli is faster and leads to smaller files
- Improves page speed
- https://brotli.pro to check if your site supports it + guides
Text Compression
- Nuxt enables GZIP out of the box with dynamic SSR
- Brotli can be enabled as well
- Usually the job of your web server or platform provider
- Pre-generate compressed assets to save time
Broken links & redirects
Technical
Effort:ย ๐ถ - ๐ถ๐ถ๐ถ
- Make sure that you don't link to broken pages
- Check for broken links to your page
- Setup redirects for them
- Avoid redirect chains (/a -> /b -> /c)
- Effort depends on site and content size
Broken links & redirects
- Redirects possible through web server / platform provider
- Also realizable via Nuxt's redirect-module
/posts/how-to-load-dynamic-images-in-vue-and-nuxt-with-ea/ /posts/dynamic-images-vue-nuxt 301
/posts/going-jamstack-with-netlify-and-nuxt/ /posts/jamstack-nuxt-netlify 301
# ...
Netlify redirects
Canonical Links
On-page
Effort:ย ๐ถ
- Set a canonical link for every page
- It represents the preferred link/version of the page
- Use the same URL for pages with duplicate content
- Especially important in e-commerce
- Great for trailing slash enforcement
https://abc.com/shoes/nike-air-max/
https://abc.com/specials/nike-air-max/
<link rel="canonical" href="https://abc.com/shoes/...">
Canonical Links
http://www.example.com
http://example.com
http://example.com/
http://www.example.com/
https://www.example.com/
https://www.example.com
https://example.com
https://example.com/
https://example.com/?foo
https://example.com/#ab
https://example.com/
Web Server's duty
Canonical Links
<script>
export default {
components: {
/* ... */
},
head () {
const baseUrl = process.env.baseUrl // retrieve URL from env
const { path } = this.$route // get current route
const pathWithSlash = path.endsWith('/') ? path : `${path}/` // append trailing slash
return {
link: [
{ rel: 'canonical', href: `${baseUrl}${pathWithSlash}` } // Set canonical
]
}
}
}
</script>
/layouts/default.vue
Implementation in Nuxt layout
Sitemap
Technical
Effort:ย ๐ถ - ๐ถ๐ถ
- Create a sitemap.xml for all relevant URLs
- Can be done automatically most of the time
- If you have many URLs (>10k), split them up
- You can also create sitemaps for images and videos
Sitemap
- Nuxt has a sitemap-module for generating URLs
sitemap: {
hostname: 'https://example.com',
gzip: true,
exclude: [
'/secret',
'/admin/**'
],
routes: [
'/page/1',
'/page/2',
{
url: '/page/3',
changefreq: 'daily',
priority: 1,
lastmod: '2017-06-30T13:30:00.000Z'
}
]
}
- Catches all static URLs, dynamic ones can be provided
- e.g. via API or file system
nuxt.config.js
Sitemap
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:mobile="http://www.google.com/schemas/sitemap-mobile/1.0" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1">
<url>
<loc>https://blog.lichter.io/posts/dynamic-images-vue-nuxt/</loc> <!-- The correct url -->
<lastmod>2019-12-05T00:03:26.000Z</lastmod> <!-- might be considered -->
<priority>1.0</priority> <!-- can be neglected -->
</url>
<!-- ofc there are more urls here in prod, lol -->
</urlset>
Robots.txt
Technical
Effort:ย ๐ถ
- Add your sitemap there
- Deny crawling of some pages
- Don't try to hide admin pages that way
- Don't block your page from being crawled
Sitemap: https://blog.lichter.io/sitemap.xml
User-agent: *
Disallow: /legal
Disallow: /privacy
User-agent: *
Disallow: /en
Optimize your assets
Technical
Effort:ย ๐ถ - ๐ถ๐ถ
-
Optimized responsive images via <picture>
-
Mind the format and size
-
Remove metadata, compress lossy
-
Load lazily
-
- Improve JS via code-splitting, lazy-loading & treeshaking
- Cache all static assets, use hashes/fingerprinting
- Use descriptive file names for images!
Page Structure
Technical
Effort:ย ๐ถ๐ถ - ๐ถ๐ถ๐ถ๐ถ
- Create a logical and hierarchical structure
- Don't nest pages too deeply (> 3 clicks)
- Make category pages worth indexing
- Link topic pages internally where possible
Home
Cat 1
Cat 2
Page 1
Page 2
Page 3
Page 4
Cat 1
Cat 2
Akryum's cat (Oreo)
URL Structure
Technical
Effort:ย ๐ถ๐ถ - ๐ถ๐ถ๐ถ๐ถ
- Use short and descriptive URLs
- Remove stop words (and, the, a, ...)
- Enforce trailing slashes or remove them
- Place keywords inside
- Avoid query params
- Use hyphens as delimiters
- non-www or www
https://abc.com/129310231.html
https://abc.com/blog_post-about_benefits_of-nuxt.html
https://abc.com/?id=129310231
https://abc.com/blog/post-about-benefits-of-nuxt.html
https://abc.com/blog/benefits-of-nuxt/
๐คฎ
๐ข
๐
๐
๐คฉ
URL Structure
export default {
/* ... */
router: {
trailingSlash: true // Will enforce slashes
}
}
Consistent trailing slashes with Nuxt
nuxt.config.js
Attention: Set up redirects for non-slash URLs
Semantic HTML
Technical / On-page
Effort:ย ๐ถ
- Use semantic HTML. For real!
- <a> when you change pages on click
- <button> when it's actionable and no link
- Use nav, footer, main, section where applicable
- Use headings appropriately & in hierarchy
<template>
<div>
<nuxt-link
v-for="(project, index) in projects"
tag="div"
@click.native="doSomethingElse">
To project
</nuxt-link>
</div>
</template>
Alt Tags
Technical / On-page
Effort:ย ๐ถ
- Provide alt tags for your images
- Don't just stuff keywords in there
- Use real descriptions of the image!
- Bonus: Can be retrieved via AI nowadays
<template>
<div>
<img src="@assets/img/cute-dog">
</div>
</template>
Meta tags
On-page
Effort: ๐ถ - ๐ถ๐ถ๐ถ๐ถ๐ถ
- Set utf-8 charset as well as the initial viewport
- Find the ideal title and meta description for each page
- Testing will be necessary but is worth it
- Use OG tags to improve link previews
- Effort depends on website size
Meta tags - Charset & Viewport
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
Can be set in nuxt.config.js or via @nuxtjs/pwa module
Using the latter will provide it by default...
...and also comes with other neat shortcuts
Meta tags - Title
Preferred format: ย ย ย ย Primary Keyword - Secondary Keyword | Brand Name
ย Going JAMstack with Netlify and Nuxt | blog.Lichter.ioย ย
export default {
head: {
titleTemplate: c => c ? `${c} | blog.Lichter.io` : 'blog.Lichter.io - Alex\'s blog about things'
}
}
Suggested length: 50 - 60 characters
Leverage vue-meta's titleTemplate
<script>
export default {
head () {
return {
title: 'Going JAMstack with Netlify and Nuxt'
}
}
}
</script>
nuxt.config.js
Define fragment in the page components
Meta tags - Meta Description
- Suggested length: 155 - 160 characters
-
Your chance to advertise the content
-
Good description -> high CTR
-
Define description in the page components
<script>
export default {
head () {
return {
title: 'Speaking'
meta: [
{
hid: 'description',
name: 'description',
content: 'Here comes the long meta description'
}
]
}
}
}
</script>
hreflang
anchor text
structured data
local seo
What to do after changing things
- Monitor, monitor, monitor!
-
Resubmit changed pages via Google Search Console for quicker updates
-
Be patient - SEO changes often take time until results show up
Conclusion
- SEO is no rocket science
-
Vue.js and SEO can be a love story
-
Always monitor changes to catch mistakes, find chances to improve, etc.
-
The only two constants in SEO are
-
It's not a one-time thing
-
As in web dev, things change quickly and often
-
Thank you!
@TheAlexLichter
Bonus - Structured Data
- Help search engines to understand your content even better...
-
By providing an easily machine-readable format of your content
-
Best way: JSON-LD
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "Blog",
"name": "blog.Lichter.io",
"url": "https://blog.lichter.io/",
"description": "A technical blog about Nuxt.js, Vue.js, Javascript, best practices, clean code and more!",
"publisher": {
"@type": "Organization",
"name": "Alexander Lichter",
"logo": {
"@type": "imageObject",
"url": "https://lichter.io/img/me@2x.jpg"
}
},
"sameAs": ["https://nuxt.xyz"],
"blogPosts": [/* ... */]
}
</script>
On-page
Effort: ๐ถ๐ถ๐ถ - ๐ถ๐ถ๐ถ๐ถ