Best practices
SEO
Edit this page on GitHubThe most important aspect of SEO is to create high-quality content that is widely linked to from around the web. However, there are a few technical considerations for building sites that rank well.
Out of the boxpermalink
SSRpermalink
While search engines have got better in recent years at indexing content that was rendered with client-side JavaScript, server-side rendered content is indexed more frequently and reliably. SvelteKit employs SSR by default, and while you can disable it in handle
, you should leave it on unless you have a good reason not to.
SvelteKit's rendering is highly configurable and you can implement dynamic rendering if necessary. It's not generally recommended, since SSR has other benefits beyond SEO.
Performancepermalink
Signals such as Core Web Vitals impact search engine ranking. Because Svelte and SvelteKit introduce minimal overhead, they make it easier to build high performance sites. You can test your site's performance using Google's PageSpeed Insights or Lighthouse. With just a few key actions like using SvelteKit's default hybrid rendering mode (which uses SSR for initial page render and CSR for subsequent navigations) and optimizing your images you can greatly improve your site's speed. Read the performance page for more details.
Normalized URLspermalink
SvelteKit redirects pathnames with trailing slashes to ones without (or vice versa depending on your configuration), as duplicate URLs are bad for SEO.
Manual setuppermalink
<title> and <meta>permalink
Every page should have well-written and unique <title>
and <meta name="description">
elements inside a <svelte:head>
. Guidance on how to write descriptive titles and descriptions, along with other suggestions on making content understandable by search engines, can be found on Google's Lighthouse SEO audits documentation.
A common pattern is to return SEO-related
data
from pageload
functions, then use it (as$page.data
) in a<svelte:head>
in your root layout.
Sitemapspermalink
Sitemaps help search engines prioritize pages within your site, particularly when you have a large amount of content. You can create a sitemap dynamically using an endpoint:
ts
export async functionGET () {return newResponse (`<?xml version="1.0" encoding="UTF-8" ?><urlsetxmlns="https://www.sitemaps.org/schemas/sitemap/0.9"xmlns:xhtml="https://www.w3.org/1999/xhtml"xmlns:mobile="https://www.google.com/schemas/sitemap-mobile/1.0"xmlns:news="https://www.google.com/schemas/sitemap-news/0.9"xmlns:image="https://www.google.com/schemas/sitemap-image/1.1"xmlns:video="https://www.google.com/schemas/sitemap-video/1.1"><!-- <url> elements go here --></urlset>`.trim (),{headers : {'Content-Type': 'application/xml'}});}
ts
export async functionGET () {return newResponse (`<?xml version="1.0" encoding="UTF-8" ?><urlsetxmlns="https://www.sitemaps.org/schemas/sitemap/0.9"xmlns:xhtml="https://www.w3.org/1999/xhtml"xmlns:mobile="https://www.google.com/schemas/sitemap-mobile/1.0"xmlns:news="https://www.google.com/schemas/sitemap-news/0.9"xmlns:image="https://www.google.com/schemas/sitemap-image/1.1"xmlns:video="https://www.google.com/schemas/sitemap-video/1.1"><!-- <url> elements go here --></urlset>`.trim (),{headers : {'Content-Type': 'application/xml',},},);}
AMPpermalink
An unfortunate reality of modern web development is that it is sometimes necessary to create an Accelerated Mobile Pages (AMP) version of your site. In SvelteKit this can be done by setting the inlineStyleThreshold
option...
ts
/** @type {import('@sveltejs/kit').Config} */constconfig = {kit : {// since <link rel="stylesheet"> isn't// allowed, inline all stylesinlineStyleThreshold :Infinity }};export defaultconfig ;
...disabling csr
in your root +layout.js
/+layout.server.js
...
ts
export constcsr = false;
ts
export constcsr = false;
...adding amp
to your app.html
<html amp>
...
...and transforming the HTML using transformPageChunk
along with transform
imported from @sveltejs/amp
:
ts
import * asamp from '@sveltejs/amp';/** @type {import('@sveltejs/kit').Handle} */export async functionhandle ({event ,resolve }) {letbuffer = '';return awaitresolve (event , {transformPageChunk : ({html ,done }) => {buffer +=html ;if (done ) returnamp .transform (buffer );}});}
ts
import * asamp from '@sveltejs/amp';import type {Handle } from '@sveltejs/kit';export consthandle :Handle = async ({event ,resolve }) => {letbuffer = '';return awaitresolve (event , {transformPageChunk : ({html ,done }) => {buffer +=html ;if (done ) returnamp .transform (buffer );},});};
To prevent shipping any unused CSS as a result of transforming the page to amp, we can use dropcss
:
ts
import * asamp from '@sveltejs/amp';importCannot find module 'dropcss' or its corresponding type declarations.2307Cannot find module 'dropcss' or its corresponding type declarations.dropcss from'dropcss' ;/** @type {import('@sveltejs/kit').Handle} */export async functionhandle ({event ,resolve }) {letbuffer = '';return awaitresolve (event , {transformPageChunk : ({html ,done }) => {buffer +=html ;if (done ) {letcss = '';constmarkup =amp .transform (buffer ).replace ('⚡', 'amp') // dropcss can't handle this character.replace (/<style amp-custom([^>]*?)>([^]+?)<\/style>/, (match ,attributes ,contents ) => {css =contents ;return `<style amp-custom${attributes }></style>`;});css =dropcss ({css ,html :markup }).css ;returnmarkup .replace ('</style>', `${css }</style>`);}}});}
ts
import * asamp from '@sveltejs/amp';importCannot find module 'dropcss' or its corresponding type declarations.2307Cannot find module 'dropcss' or its corresponding type declarations.dropcss from'dropcss' ;import type {Handle } from '@sveltejs/kit';export consthandle :Handle = async ({event ,resolve }) => {letbuffer = '';return awaitresolve (event , {transformPageChunk : ({html ,done }) => {buffer +=html ;if (done ) {letcss = '';constmarkup =amp .transform (buffer ).replace ('⚡', 'amp') // dropcss can't handle this character.replace (/<style amp-custom([^>]*?)>([^]+?)<\/style>/, (match ,attributes ,contents ) => {css =contents ;return `<style amp-custom${attributes }></style>`;});css =dropcss ({css ,html :markup }).css ;returnmarkup .replace ('</style>', `${css }</style>`);}},});};
It's a good idea to use the
handle
hook to validate the transformed HTML usingamphtml-validator
, but only if you're prerendering pages since it's very slow.