@eight25-factory/seo-essentials

SEO essentials for Next.js and Nuxt with Contentful or Contentstack: per-entry metadata into , environment-aware robots.txt, XML sitemap that skips untranslated locales, canonical URLs, og:type QA, and CMS field provisioning. Wires itself on install
1,238
Install
npm install @eight25-factory/seo-essentials
Latest Version:0.8.0
Vue:3
Last Updated:Sep 17, 2026
Links: npm
Maintainer: nishy-eight25

@eight25-factory/seo-essentials

Per-entry metadata into <head>, an XML sitemap that lists only published, translated, indexable pages, an environment-aware robots.txt, canonical URLs, and an og:type QA — for Next.js and Nuxt, with Contentful or Contentstack. Plus SKILLS.md, an instruction file that teaches an AI agent to keep every new page SEO-correct.

npm install               → the package detects the app and wires itself (postinstall)
Editor fills the SEO tab  → title, description, canonical, og:*, twitter:*, robots in <head>
Publishes a page          → it appears in that language's sitemap (/sitemaps/<locale>.xml, listed by /sitemap.xml)
Preview / staging deploy  → robots.txt says Disallow: /, every response says X-Robots-Tag: noindex
Production                → Allow, Sitemap: line, indexable

Install

One command in the project root. The package's postinstall runs init --yes --write for you: it detects Next or Nuxt, the CMS (from live-preview.config.mjs, CMS_PROVIDER, credential variable names or the SDK in package.json), asks the CMS for its locales when delivery credentials are in .env*, and writes the wiring:

npm install @eight25-factory/seo-essentials

What lands in the project (Next.js; Nuxt paths in docs/nuxt.md):

File Purpose
seo-essentials.config.mjs Application-owned, non-secret configuration (site, locales, robots, …)
.env.seo-essentials.example The variable names the generated files read
lib/seo/config.ts Parsed configuration + seoEnvironment()
lib/seo/server.ts pageMetadata(entry, { path }), rootMetadata(), notFoundMetadata()
lib/seo/pages.ts Lists published pages from the CMS Delivery API (native fetch, no SDK)
app/sitemap.xml/route.ts The sitemap (index of the language sitemaps when multi-locale)
app/sitemaps/[file]/route.ts One sitemap per language: /sitemaps/en-us.xml, /sitemaps/de-de.xml
app/robots.txt/route.ts The robots route
app/sitemap.xsl/route.ts Browser view of the sitemaps: formatted, clickable (crawlers get XML)
.claude/skills/seo-essentials/ The AI skill, plus a pointer in AGENTS.md

It also patches next.config.* (default export wrapped in withSeoEssentials, which adds X-Robots-Tag: noindex, nofollow outside production) and app/layout.tsx (export const metadata = rootMetadata({…})) when they have the create-next-app shape, and reports anything it left alone. Existing files are never overwritten.

Then, once, create the SEO fields in the CMS with the management token from .env and add demo values so there is something to look at:

npx seo-essentials provision --seed        # Contentful: `seo` content type + link on every page type
                                           # Contentstack: `seo` global field + reference on every page type

Prefer to see the plan first? npx seo-essentials init (no --write) prints every file; npx seo-essentials provision --dry-run prints every CMS change. pnpm and --ignore-scripts do not run dependency postinstalls: run npx seo-essentials init --write yourself.

Per-page metadata

Wired for you on Next: init patches every dynamic page route under app/ ([slug], [...slug], [[...slug]], with or without a [locale] segment) to export

// app/[[...slug]]/page.tsx — added by init
import { routeMetadata, type RouteParams } from "@/lib/seo/server";

export async function generateMetadata({
  params,
}: {
  params: Promise<RouteParams>;
}): Promise<Metadata> {
  return routeMetadata("/[[...slug]]", await params);
}

routeMetadata turns the route pattern and params into the page path and locale, fetches the entry whose URL field holds that path from the Delivery API (SEO group included, cached for five minutes), and emits title (templated), description, canonical, robots, Open Graph (with a valid og:type), Twitter and hreflang. The page query needs no change. A route that already had a generateMetadata keeps it: it is renamed generateMetadataWithoutSeo and the CMS metadata is layered over its result.

A route that loads its own entry can call pageMetadata(entry, { path, locale }) instead. It accepts the raw entry in any delivery shape (GraphQL object, REST { sys, fields }, Contentstack entry) and reads the SEO group by the conventional names — seo.metaTitle (Contentful) or seo.meta_title (Contentstack), plus legacy flat fields such as seoNoIndex — falling back to the page title and the site defaults.

Nuxt: usePageSeo(page, { path: route.path }) from the generated composable.

What it does

  • Metadata in one call: title template, description, canonical, robots, og:*, twitter:*, hreflang + x-default, keywords; fallbacks are deliberate and reported as warnings.
  • One sitemap per language: with more than one locale /sitemap.xml is a sitemap index listing /sitemaps/<locale>.xml for every language that has pages; each file carries that locale's URLs with xhtml:link alternates. Single-locale sites get one flat file.
  • Sitemap that tells the truth: only published entries that answer 200 (each URL is checked, cached an hour; 404/410 and redirects drop out); per locale only when the entry is actually translated (Contentful: the entry stores its own value for translationField in that locale; Contentstack: the entry did not fall back); never noIndex or excludeFromSitemap pages; never duplicates; xhtml:link alternates grouped per entry; lastmod from the CMS; static paths appended; chunking helpers for > 50,000 URLs.
  • Environment-aware robots: production (from SEO_ENVIRONMENT, then VERCEL_ENV, CONTEXT; never NODE_ENV) serves your policy plus the Sitemap: line; anything else serves Disallow: / and adds X-Robots-Tag: noindex, nofollow to every response.
  • Canonicals with a policy: lowercase, no trailing slash, no query (configurable); entry-level override for deliberate duplicates; og:url and sitemap loc always equal the canonical.
  • og:type QA: validateOgType classifies a stored value as valid, normalizable (Web page → website) or invalid; the helper renders the fix and reports it; npx seo-essentials audit <url> --sitemap fails a deployment on invalid values after a migration.
  • CMS provisioning: provision creates the fields (idempotent) and, with --seed, demo values — published, with one page localized on request to demonstrate the translation rule.
  • AI skill: SKILLS.md with rules, recipes, a verification protocol and a diagnosis table, installed by init and by npx seo-essentials install-skills.

What it deliberately does not do

  • No runtime dependency on a CMS SDK or a framework. The package is pure functions; the Delivery API calls live in the generated lib/seo/pages.ts inside your app, where you can read and change them.
  • No JSON-LD / structured data. That is content-type specific; add it in the page next to pageMetadata.
  • No redirects or URL rewriting. Canonicals describe URLs; routing owns them.
  • No NEXT_PUBLIC_* tokens. Only the Delivery token is read, on the server, by name.

Verify

With the app running:

npx seo-essentials audit http://localhost:3000            # one page's <head>
npx seo-essentials audit http://localhost:3000 --sitemap  # robots.txt + sitemap + every listed page
npx seo-essentials check-env                              # which variable names are set (never values)

Supported integrations

Integration Entry point
Next.js 13+ @eight25-factory/seo-essentials/next
Nuxt 3+ @eight25-factory/seo-essentials/nuxt
Contentful @eight25-factory/seo-essentials/contentful
Contentstack @eight25-factory/seo-essentials/contentstack
Framework-neutral @eight25-factory/seo-essentials
Generated files @eight25-factory/seo-essentials/templates
CLI npx seo-essentials
AI skill SKILLS.md, npx seo-essentials install-skills

Documentation

Document What it covers
SKILLS.md How to keep pages, sitemap and robots correct
AGENTS.md Instructions for an agent wiring the package
docs/nextjs.md Complete Next.js wiring
docs/nuxt.md Complete Nuxt wiring
docs/contentful.md Contentful: fields, queries, translation rule
docs/contentstack.md Contentstack: global field, queries, fallback rule
docs/setup-questions.md The install questions and what answers them
docs/default-configuration.md Every option and its default
docs/api-reference.md Every public export
docs/lifecycle.md What runs where, and when
docs/troubleshooting.md Symptom → cause
docs/cli.md Every CLI command
docs/local-install.md Vendored installation
DEVELOPMENT.md Working on this package

Requirements

Node 22 or newer. No runtime dependencies.

Related Packages

@easeo/nuxt

easeo integration for Nuxt

477 5
@eight25-factory/live-preview

CMS live preview, live updates, and inspector-mode field tagging for Next.js and...

159
@ranklint/nuxt

Nuxt 4 SEO module: sitemap.xml, robots.txt, typed JSON-LD and a live SEO panel i...

1,849 2
routelint

Find technical SEO problems in your deployed site: broken links, incorrect redir...

980 0
rankforge-cli

Deterministic technical-SEO audit from your terminal. Crawls a site, measures re...

374 0

Version History

Version Released License
0.8.0
0.7.0
0.6.0
0.5.0
0.3.0