@northsoon/astro-seo
v3.0.0
Published
SEO component for Astro with full TypeScript support, auto-generated .d.ts declaration files, and IDE autocompletion
Maintainers
Readme
@northsoon/astro-seo
🚀 SEO component for Astro with full TypeScript support.
An enhanced, maintained version of @astrolib/seo with properly exported TypeScript types, declaration files (.d.ts), and better documentation.
v3.0.0 — Major release: typed JSON-LD, full Twitter Cards support, stricter
MetaTagdiscriminated union, fix fortitleTemplatewith$sequences, and dev-mode URL warnings. See Migration from v2 and the changelog for details.
✨ Features
- ✅ Full TypeScript support with auto-generated
.d.tsdeclaration files - ✅ IDE autocompletion for all props, including
@context/@typefor JSON-LD - ✅ Type validation —
MetaTagis a true discriminated union, errors caught before runtime - ✅ Open Graph support (Facebook, LinkedIn, etc.)
- ✅ Twitter Cards —
cardType,site,handle,title,description,image,imageAlt - ✅ Customizable robots meta tags
- ✅ Canonical URLs (with dev-mode warning when relative)
- ✅ Language alternates (hreflang)
- ✅ Custom additional meta tags
- ✅ Custom additional link tags
- ✅ Compatible with Astro 4.x, 5.x, and 6.x
- ✅ Compatible with TypeScript 5.x and 6.x
- ✅ Unit tested with Vitest (53 tests)
- ✅ JSON-LD support — pass any Schema.org object as a prop, rendered as XSS-safe
<script type="application/ld+json">
🆕 What's New in v3
Twitter Cards: full support
<AstroHead
twitter={{
cardType: "summary_large_image",
site: "@mysite",
handle: "@author",
title: "Tweet-specific title", // optional, falls back to og:title
description: "Tweet-specific summary", // optional, falls back to og:description
image: "https://mysite.com/twitter.png",
imageAlt: "Alt text for accessibility",
}}
/>Only the fields you set are emitted — Twitter natively falls back to og:* for anything you omit, so there's no duplication.
JSON-LD with autocomplete
import type { JsonLdObject } from "@northsoon/astro-seo";
const orgSchema: JsonLdObject = {
"@context": "https://schema.org", // autocompletes
"@type": "Organization", // autocompletes common Schema.org types
name: "Northsoon Studio",
url: "https://northsoon.com",
};Dev-mode URL warnings
canonical, openGraph.url, openGraph.images[].url, openGraph.videos[].url, and twitter.image log a console.warn in dev when given relative URLs (Google requires absolute canonicals; social crawlers can't resolve relative either). Silent in production.
Stricter MetaTag typing
additionalMetaTags[] entries must now declare exactly one of name, property, or httpEquiv — TypeScript rejects entries that mix or omit them, and runtime skips invalid entries (with a dev warning) instead of emitting bad HTML.
🔧 How TypeScript Types Work
This package includes pre-built declaration files (.d.ts) in the dist/ folder. When you install the package:
- TypeScript automatically finds the types via
package.json→"types": "./dist/index.d.ts" - No manual setup required - just import and use
- Full autocompletion in VS Code, WebStorm, and other IDEs
- Type checking validates your props at compile time
@northsoon/astro-seo/
├── index.ts ← Source code
├── dist/
│ ├── index.d.ts ← Type declarations (auto-detected)
│ └── src/
│ ├── types.d.ts ← Core SEO types
│ └── types-extended.d.ts ← Schema.org helper types📦 Installation
# npm
npm install @northsoon/astro-seo
# pnpm
pnpm add @northsoon/astro-seo
# yarn
yarn add @northsoon/astro-seo🚀 Quick Start
---
import { AstroHead } from "@northsoon/astro-seo";
---
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<AstroHead
title="My Page"
description="Page description for SEO (150-160 characters)"
canonical="https://mysite.com/page"
/>
</head>📖 Full Example
---
import { AstroHead } from "@northsoon/astro-seo";
---
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<AstroHead
title="My Amazing Page"
titleTemplate="%s | My Site"
description="A detailed description of my page for search engines"
canonical="https://mysite.com/page"
noindex={false}
nofollow={false}
openGraph={{
url: "https://mysite.com/page",
title: "My Amazing Page",
description: "A detailed description for social media",
type: "website",
locale: "en_US",
site_name: "My Website",
images: [
{
url: "https://mysite.com/og-image.jpg",
width: 1200,
height: 630,
alt: "Preview image",
type: "image/jpeg",
},
],
}}
twitter={{
cardType: "summary_large_image",
site: "@mysite",
handle: "@myhandle",
// Optional — only emit when you want different values than og:*
title: "Tweet-specific title",
description: "Tweet-specific summary (Twitter falls back to og:description otherwise)",
image: "https://mysite.com/twitter-card.jpg",
imageAlt: "Preview image alt text",
}}
languageAlternates={[
{ hreflang: "en", href: "https://mysite.com/en/page" },
{ hreflang: "es", href: "https://mysite.com/es/page" },
{ hreflang: "x-default", href: "https://mysite.com/page" },
]}
additionalMetaTags={[
{ name: "author", content: "Your Name" },
{ name: "theme-color", content: "#ffffff" },
]}
additionalLinkTags={[
{ rel: "icon", href: "/favicon.ico" },
{ rel: "apple-touch-icon", href: "/apple-touch-icon.png", sizes: "180x180" },
{ rel: "manifest", href: "/site.webmanifest" },
]}
/>
</head>🔧 Available Props
| Prop | Type | Description |
| -------------------- | ----------------------- | --------------------------------------------------- |
| title | string | Page title |
| titleTemplate | string | Title template (use %s as placeholder) |
| description | string | Meta description |
| canonical | string | Canonical URL |
| noindex | boolean | If true, tells search engines not to index |
| nofollow | boolean | If true, tells search engines not to follow links |
| robotsProps | AdditionalRobotsProps | Additional robots directives |
| openGraph | OpenGraph | Open Graph configuration |
| twitter | Twitter | Twitter Cards configuration |
| facebook | { appId: string } | Facebook App ID |
| mobileAlternate | MobileAlternate | Mobile alternate version |
| languageAlternates | LanguageAlternate[] | Language alternate versions |
| additionalMetaTags | MetaTag[] | Additional meta tags |
| additionalLinkTags | LinkTag[] | Additional link tags |
| jsonLd | JsonLdObject \| JsonLdObject[] | Schema.org structured data (single object or array) |
🤖 Robots Configuration
<AstroHead
title="My Page"
noindex={false}
nofollow={false}
robotsProps={{
nosnippet: false,
maxSnippet: -1,
maxImagePreview: "large",
maxVideoPreview: -1,
noarchive: false,
noimageindex: false,
notranslate: false,
}}
/>📱 Open Graph for Articles
<AstroHead
title="My Blog Article"
openGraph={{
type: "article",
article: {
publishedTime: "2025-12-11T00:00:00Z",
modifiedTime: "2025-12-11T12:00:00Z",
authors: ["https://mysite.com/author"],
section: "Technology",
tags: ["Astro", "SEO", "TypeScript"],
},
}}
/>🎥 Open Graph for Videos
<AstroHead
title="My Video"
openGraph={{
type: "video.other",
video: {
actors: [{ profile: "https://example.com/actor", role: "Lead" }],
directors: ["https://example.com/director"],
duration: 120,
releaseDate: "2025-12-11",
tags: ["tutorial", "astro"],
},
}}
/>📊 JSON-LD (Structured Data)
Pass any Schema.org object to generate a <script type="application/ld+json"> tag. This enables Google Rich Results (star ratings, breadcrumbs, FAQ dropdowns, etc.).
<AstroHead
title="Northsoon Studio"
jsonLd={{
"@context": "https://schema.org",
"@type": "LocalBusiness",
"name": "Northsoon Studio",
"url": "https://northsoon.com",
"telephone": "+1-780-555-0100",
"address": {
"@type": "PostalAddress",
"addressLocality": "Grande Prairie",
"addressRegion": "AB",
"addressCountry": "CA"
}
}}
/>For multiple schemas on the same page, pass an array:
<AstroHead
title="My Site"
jsonLd={[
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Northsoon Studio",
"url": "https://northsoon.com"
},
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "Northsoon Studio",
"url": "https://northsoon.com"
}
]}
/>Validate your structured data at Google Rich Results Test.
📝 TypeScript Types
All types are exported and work automatically - no additional setup needed!
import { AstroHead } from "@northsoon/astro-seo";
import type {
// Core SEO types
AstroSeoProps,
OpenGraph,
OpenGraphArticle,
OpenGraphMedia,
Twitter,
MetaTag,
HTTPEquivValue,
LinkTag,
LanguageAlternate,
AdditionalRobotsProps,
// JSON-LD types (v3)
JsonLdObject,
SchemaOrgType,
// Schema.org helper types (for JSON-LD shapes)
ContactPoint,
OpeningHoursSpecification,
Offer,
AggregateRating,
Review,
Video,
} from "@northsoon/astro-seo";
// TypeScript validates your props
const seoConfig: AstroSeoProps = {
title: "My Page",
description: "Page description",
openGraph: {
type: "website",
site_name: "My Site", // ✅ Autocompletion works!
},
};What You Get
| Feature | Description | | ----------------------- | --------------------------------- | | Autocompletion | All props show up in your IDE | | Type checking | Errors caught before runtime | | Hover documentation | See prop descriptions on hover | | Refactoring support | Safe renaming across your project |
Verifying Types Work
Run astro check in your project to verify:
npx astro check
# Should show: 0 errors ✓🔁 Migration from v2
v3.0.0 is mostly additive, but a few changes can surface type errors in code that compiled under v2:
MetaTagis now a true discriminated union. Entries inadditionalMetaTagsmust declare exactly one ofname,property, orhttpEquiv. Entries missing all three were silently emitted as invalid<meta content="…">in v2 — in v3 they are skipped (with a dev warning) and TypeScript will flag them at build time.jsonLdis now typed asJsonLdObjectinstead ofRecord<string, unknown>. Existing usage keeps working —JsonLdObjectis{ "@context"?, "@type"? } & Record<string, unknown>— but you now get IDE autocomplete for@contextand common Schema.org@typevalues.- Internal file renamed:
src/AstroSeo.astro→src/AstroHead.astro. Public API unchanged (stillimport { AstroHead }). Only affects code doing deep imports.
No runtime behavior changed for code that was already correctly typed.
🔄 Migration from v1
Component rename
In v2.0.0 the component was renamed from AstroSeo to AstroHead to have a unique identity:
- import { AstroSeo } from "@northsoon/astro-seo";
+ import { AstroHead } from "@northsoon/astro-seo";- <AstroSeo title="..." />
+ <AstroHead title="..." />Removed props
OpenGraph.defaultImageWidth and OpenGraph.defaultImageHeight have been removed. They were accepted by TypeScript but never rendered any tag — silently doing nothing. Remove them from your config:
openGraph={{
type: "website",
- defaultImageWidth: 1200,
- defaultImageHeight: 630,
images: [{ url: "...", width: 1200, height: 630 }],
}}Use images[].width and images[].height instead — those are the props that actually generate the og:image:width and og:image:height tags.
Bug fix: maxVideoPreview now works
In v1, setting robotsProps.maxVideoPreview was silently ignored. In v2.0.0 it correctly generates max-video-preview:N in the robots meta tag.
🔄 Migration from @astrolib/seo
If you are coming from @astrolib/seo:
- import { AstroSeo } from "@astrolib/seo";
+ import { AstroHead } from "@northsoon/astro-seo";Props are 100% compatible!
🛠 Troubleshooting
TypeScript can't find types
Make sure you have TypeScript configured in your Astro project:
npx astro add checkThis installs @astrojs/check and typescript if missing.
IDE not showing autocompletion
- Restart your TypeScript server (VS Code:
Ctrl+Shift+P→ "TypeScript: Restart TS Server") - Make sure you're on version
3.0.0or higher
Verify installation
# Check installed version
npm list @northsoon/astro-seo
# Should show @northsoon/[email protected] or higher📋 Changelog
v3.0.0
- Feat: Full Twitter Cards support —
twitter.title,twitter.description,twitter.image,twitter.imageAlt - Feat:
JsonLdObjecttype —@contextand@typeautocomplete for thejsonLdprop, withSchemaOrgTypehelper - Feat: Dev-mode
console.warnwhencanonical,openGraph.url, OG media URLs, ortwitter.imageare relative - Fix:
titleTemplateno longer interprets$&,$1,$$intitleas replacement patterns (usessplit/joininstead ofreplaceAll) - Fix:
additionalMetaTagsentries withoutname/property/httpEquivare skipped (and warn in dev) instead of emitting invalid<meta> - Refactor: Unified
createMetaTag/createLinkTagthrough a singlecreateTaghelper - Refactor:
MetaTagis now a true discriminated union — TypeScript rejects entries that mix or omit discriminators - Refactor:
buildJsonLduses a single-pass regex for the XSS escape (same guarantee, fewer allocations) - Chore: Renamed
src/AstroSeo.astro→src/AstroHead.astroto match the exported name - Test: added 9 new tests —
$®ression, http-equiv tags, invalidadditionalMetaTags, Twitter title/description/image/imageAlt, no-duplication fallback
v2.1.2
- Security fix:
buildJsonLdnow escapes<,>and&as Unicode sequences (\u003C,\u003E,\u0026) to prevent XSS injection via early</script>tag closing - Fix:
types-extended.tstypes (ContactPoint,OpeningHoursSpecification,Offer,AggregateRating,Review,Video, and more) are now exported from the public API and fully accessible to TypeScript consumers - Improvement:
Twitter.cardTypenow has IDE autocompletion with valid values ("summary","summary_large_image","app","player") while still accepting any string - Test: added 5 new unit tests covering
facebook.appId,mobileAlternate,additionalLinkTags, and XSS protection indescription - Chore: added
.npmrcwithnode-linker=hoistedto fix@vitest/utilsresolution issue on Windows with pnpm + Node.js v24
v2.1.0
- Feat:
jsonLdprop — pass any Schema.org object (or array of objects) to generate a<script type="application/ld+json">tag automatically - Test: added 6 unit tests for
buildJsonLdcovering LocalBusiness, WebSite, BreadcrumbList, FAQPage, and arrays
v2.0.5
- Fix:
titleTemplatenow usesreplaceAll— correctly replaces all occurrences of%sinstead of only the first - Test: added unit tests with Vitest covering title, description, robots, canonical, Open Graph, Twitter, hreflang, and additionalMetaTags
v2.0.4
- Fix:
src/astro.d.tsandsrc/env.d.ts(dev-only files) no longer shipped in the npm package — prevents potential type conflicts in user projects - Fix:
package.jsonfilesfield is now explicit, only shipping the files users actually need
v2.0.3
- Fix: resolved
FragmentTS2304 error in VS Code Astro language server when working on the library
v2.0.2
- Updated TypeScript to 6.0.3 (build tool only — no impact on your project's TypeScript version)
v2.0.1
- Fix: peer dependency updated to
"^4.0.0 || ^5.0.0 || ^6.0.0"— removes Astro 6 install warning
v2.0.0
- Breaking: Renamed
AstroSeo→AstroHead - Breaking: Removed
OpenGraph.defaultImageWidth/defaultImageHeight(were silently ignored) - Breaking: Removed unused JSON-LD types (
Person,Answer,Question, etc.) - Fix:
maxVideoPreviewnow correctly generatesmax-video-preview:Nin the robots tag
📄 License
MIT © Manuel Caballero
Made with ❤️ by Northsoon
⭐ If this package helps you, consider giving it a star on GitHub!
