next-metadata-toolkit
v0.2.0
Published
Thin wrapper around Next.js App Router Metadata API with typed JSON-LD helpers.
Maintainers
Readme
@andesphere/next-seo
Thin, type-safe helpers for the Next.js App Router Metadata API plus JSON-LD utilities.
- Wraps Next.js
Metadatawith sensible defaults - Preserves global Open Graph/Twitter defaults via deep merge
- Typed JSON-LD helpers using
schema-dts(types only) - Zero runtime dependencies
Install
npm install @andesphere/next-seo
# or
pnpm add @andesphere/next-seo
# or
yarn add @andesphere/next-seoQuick Start
1) Global config (root layout)
// app/layout.tsx
import { createSeoConfig } from '@andesphere/next-seo';
export const metadata = createSeoConfig({
siteName: 'Andy Partner',
titleTemplate: '%s | Andy Partner',
defaultTitle: 'Andy Partner – AI Chatbot for Small Business',
defaultDescription: 'Custom AI chatbots that help small businesses capture and convert leads.',
baseUrl: 'https://andypartner.com',
defaultOgImage: '/og-default.png',
twitterHandle: '@andypartner',
locale: 'en_US',
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}2) Per-page metadata
// app/blog/[slug]/page.tsx
import { makePageMetadata } from '@andesphere/next-seo';
export async function generateMetadata({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug);
return makePageMetadata({
title: post.title,
description: post.excerpt,
canonical: `/blog/${post.slug}`,
openGraph: {
type: 'article',
images: [{ url: post.coverImage }],
publishedTime: post.publishedAt,
},
});
}Tip: If you have the pathname but not a canonical URL, you can pass pathname instead:
makePageMetadata({
title: 'Pricing',
description: 'Simple pricing for growing teams.',
pathname: '/pricing',
});3) JSON-LD
// app/page.tsx
import { JsonLd, organizationSchema, websiteSchema } from '@andesphere/next-seo';
export default function HomePage() {
return (
<>
<JsonLd
data={organizationSchema({
name: 'Andy Partner',
url: 'https://andypartner.com',
logo: 'https://andypartner.com/logo.png',
sameAs: [
'https://twitter.com/andypartner',
'https://linkedin.com/company/andypartner',
],
})}
/>
<JsonLd
data={websiteSchema({
name: 'Andy Partner',
url: 'https://andypartner.com',
searchUrl: 'https://andypartner.com/search?q={search_term_string}',
})}
/>
{/* Page content */}
</>
);
}// app/blog/[slug]/page.tsx
import { JsonLd, articleSchema, breadcrumbSchema } from '@andesphere/next-seo';
export default function BlogPost({ post }: { post: Post }) {
return (
<>
<JsonLd
data={articleSchema({
headline: post.title,
description: post.excerpt,
image: post.coverImage,
datePublished: post.publishedAt,
dateModified: post.updatedAt,
author: { name: post.author.name },
publisher: {
name: 'Andy Partner',
logo: 'https://andypartner.com/logo.png',
},
})}
/>
<JsonLd
data={breadcrumbSchema([
{ name: 'Home', url: '/' },
{ name: 'Blog', url: '/blog' },
{ name: post.title, url: `/blog/${post.slug}` },
])}
/>
{/* Post content */}
</>
);
}API
createSeoConfig(input)
Builds global defaults for your root layout and stores them for page-level merging.
import type { SeoConfigInput } from '@andesphere/next-seo';Key options:
siteName: Site or brand name (required)titleTemplate: Title template (default:%s | ${siteName})defaultTitle: Default title (default:siteName)defaultDescription: Default meta descriptionbaseUrl: Absolute site URL (required). SetsmetadataBasedefaultOgImage: Default Open Graph/Twitter image (relative or absolute)twitterHandle:@handlefor Twitter metadatalocale: Open Graph locale (e.g.en_US)
makePageMetadata(input)
Creates page-level metadata and deep-merges Open Graph/Twitter with the global defaults.
Additional fields:
canonical: Canonical URL or path (relative ok whenmetadataBaseis set)pathname: Convenience for generating canonical URLs from a path
<JsonLd />
Server Component to render JSON-LD:
<JsonLd data={organizationSchema({ name: 'Example', url: 'https://example.com' })} />Schema helpers
All helpers return WithContext<T> objects from schema-dts:
organizationSchema()websiteSchema()(optionalSearchAction)productSchema()(includesOffer)articleSchema()faqPageSchema()breadcrumbSchema()
Notes
metadataBaseis required for correct absolute URL generation. Always passbaseUrltocreateSeoConfig.- Open Graph images work best at
1200x630. - Next.js merges metadata shallowly. This package deep-merges Open Graph and Twitter for you.
- For dynamic pages, consider
React.cache()to share data betweengenerateMetadataand the page component. - FAQ pages typically perform best with ~10 questions or fewer.
License
MIT
