SEO
Svelar ships a <Seo> component that handles meta tags, Open Graph, Twitter Cards, canonical URLs, robots directives, and JSON-LD structured data — all from a single component.
SvelteKit renders pages server-side by default, so crawlers see your full HTML content without needing JavaScript. The <Seo> component builds on this by injecting the right <head> tags.
Quick Start
The scaffold already includes <Seo> in the root layout with site-wide defaults:
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { Seo } from '@beeblock/svelar/ui';
</script>
<Seo
title="My App"
description="My App — built with Svelar, the Laravel of SvelteKit."
ogSiteName="My App"
ogType="website"
/>
Override per page by adding another <Seo> — SvelteKit merges <svelte:head> blocks, with page-level tags taking priority:
<!-- src/routes/about/+page.svelte -->
<script lang="ts">
import { Seo } from '@beeblock/svelar/ui';
</script>
<Seo
title="About Us — My App"
description="Learn about our team and mission."
ogImage="/images/about-og.png"
canonical="https://myapp.com/about"
/>
Props Reference
Basic
| Prop | Type | Description |
|---|---|---|
title |
string |
Page <title> — also used as fallback for OG/Twitter titles |
description |
string |
<meta name="description"> — also fallback for OG/Twitter descriptions |
keywords |
string |
<meta name="keywords"> (comma-separated) |
canonical |
string |
<link rel="canonical"> — also fallback for og:url |
Open Graph
| Prop | Type | Default | Description |
|---|---|---|---|
ogTitle |
string |
Falls back to title |
og:title |
ogDescription |
string |
Falls back to description |
og:description |
ogImage |
string |
— | og:image (use absolute URL for best compatibility) |
ogUrl |
string |
Falls back to canonical |
og:url |
ogType |
string |
'website' |
og:type — use 'article' for blog posts |
ogSiteName |
string |
— | og:site_name |
ogLocale |
string |
— | og:locale (e.g., 'en_US') |
Twitter Card
| Prop | Type | Default | Description |
|---|---|---|---|
twitterCard |
string |
'summary_large_image' |
Card type: 'summary', 'summary_large_image', 'app', 'player' |
twitterSite |
string |
— | Your site's Twitter handle (e.g., '@myapp') |
twitterCreator |
string |
— | Content author's Twitter handle |
twitterTitle |
string |
Falls back to ogTitle then title |
Title shown in Twitter card |
twitterDescription |
string |
Falls back to ogDescription then description |
Description in Twitter card |
twitterImage |
string |
Falls back to ogImage |
Image in Twitter card |
Robots
| Prop | Type | Default | Description |
|---|---|---|---|
robots |
string |
— | Full robots directive (overrides noindex/nofollow) |
noindex |
boolean |
false |
Add noindex to robots |
nofollow |
boolean |
false |
Add nofollow to robots |
Structured Data
| Prop | Type | Description |
|---|---|---|
jsonLd |
Record<string, any> |
JSON-LD object — rendered as <script type="application/ld+json"> |
Extra Head Content
The <Seo> component also accepts children for injecting additional <svelte:head> content:
<Seo title="My Page" description="...">
<link rel="alternate" hreflang="es" href="https://myapp.com/es/page" />
<meta property="article:published_time" content="2026-01-15" />
</Seo>
Patterns
Blog Post with Article Metadata
<script lang="ts">
import { Seo } from '@beeblock/svelar/ui';
let { data } = $props();
const { post } = data;
</script>
<Seo
title="{post.title} — My Blog"
description={post.excerpt}
ogType="article"
ogImage={post.coverImage}
canonical="https://myapp.com/blog/{post.slug}"
jsonLd={{
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
description: post.excerpt,
image: post.coverImage,
datePublished: post.createdAt,
dateModified: post.updatedAt,
author: {
'@type': 'Person',
name: post.author.name,
},
}}
/>
Product Page with JSON-LD
<Seo
title="{product.name} — My Store"
description={product.shortDescription}
ogImage={product.images[0]}
canonical="https://mystore.com/products/{product.slug}"
jsonLd={{
'@context': 'https://schema.org',
'@type': 'Product',
name: product.name,
description: product.shortDescription,
image: product.images,
offers: {
'@type': 'Offer',
price: product.price,
priceCurrency: 'USD',
availability: product.inStock
? 'https://schema.org/InStock'
: 'https://schema.org/OutOfStock',
},
}}
/>
SaaS Landing Page with Organization
<Seo
title="My SaaS — Ship faster with Svelar"
description="The fastest way to build modern SaaS applications."
ogImage="https://myapp.com/og-home.png"
canonical="https://myapp.com"
twitterSite="@myapp"
jsonLd={{
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'My SaaS',
url: 'https://myapp.com',
logo: 'https://myapp.com/logo.png',
sameAs: [
'https://twitter.com/myapp',
'https://github.com/myapp',
],
}}
/>
Dynamic Title from Load Data
<!-- +page.server.ts -->
<script lang="ts" context="module">
export async function load({ params }) {
const user = await User.findOrFail(params.id);
return { user };
}
</script>
<!-- +page.svelte -->
<script lang="ts">
import { Seo } from '@beeblock/svelar/ui';
let { data } = $props();
</script>
<Seo
title="{data.user.name}'s Profile"
description="{data.user.name} on My App."
noindex
/>
Preventing Indexing
For admin pages, dashboard, or draft content:
<Seo title="Admin Panel" noindex nofollow />
Or with a custom robots string:
<Seo title="Archive" robots="noindex, follow" />
Sitemap
For automatic XML sitemap generation, use the @beeblock/svelar-sitemap plugin:
npx svelar plugin:install @beeblock/svelar-sitemap
See the Sitemap Plugin docs for configuration.
robots.txt
Add a static/robots.txt file to your project:
User-agent: *
Allow: /
Sitemap: https://myapp.com/sitemap.xml
For dynamic robots.txt, create a SvelteKit endpoint:
// src/routes/robots.txt/+server.ts
import type { RequestHandler } from './$types';
export const GET: RequestHandler = () => {
const body = `User-agent: *
Allow: /
Disallow: /admin
Disallow: /dashboard
Disallow: /api/
Sitemap: https://myapp.com/sitemap.xml`;
return new Response(body, {
headers: { 'Content-Type': 'text/plain' },
});
};
Best Practices
- Always set
titleanddescriptionon every page — these are the most important tags for search engines - Use absolute URLs for
ogImage,canonical, andogUrl— relative URLs don't work reliably with social media crawlers - Keep titles under 60 characters and descriptions under 160 characters
- Set
canonicalon pages with query parameters to avoid duplicate content (e.g., paginated lists) - Use
noindexon private pages — dashboard, admin, settings, etc. - Add JSON-LD structured data for content pages — articles, products, events, FAQs — to get rich snippets in search results
- Set
ogImagedimensions to 1200x630px for optimal display on social platforms - Use
og:type="article"for blog posts andog:type="website"for everything else - Set
ogSiteNameonce in the root layout — it applies globally - Test your meta tags with Google Rich Results Test and social media debuggers (Twitter Card Validator, Facebook Sharing Debugger)