pseo-data
Skill54 starsChanged 15 days agoMITView on GitHub
Design and implement the structured data architecture that powers programmatic SEO pages, including content models, data sources, slug generation, and data-fetching layers. Use when setting up or refactoring the data foundation for pSEO, designing content models, or building the data pipeline that feeds page templates.
1 other project uses a version of this, 1 of them edited.
Adopted by
Most recently taken up by
- karrolcia/hippocampusedited, first seen Mar 2, 2026
Other versions1 project uses a version of this.
Word for word · 0
None found.
Edited · 1
- karrolcia/hippocampus★ 1394% same
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository17 other files it gives its agents.
- gscSkill
- pseo-auditSkill
- pseo-discoverySkill
- pseo-linkingSkill
- pseo-llm-visibilitySkill
- pseo-metadataSkill
- pseo-orchestrateSkill
- pseo-performanceSkill
- pseo-quality-guardSkill
- pseo-scaleSkill
- pseo-schemaSkill
- pseo-templatesSkill
- seo-content-refreshSkill
- seo-i18nSkill
- seo-log-analysisSkill
- seo-migrationSkill
- seo-regression-guardSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
23 rules. About 0 are common, 18 are distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈18
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 14
Single source of truth: All page data flows from one data layer
- Distinctive: line 15
SEO-complete models: Every content model includes all fields needed for metadata, schema markup, and linking
- Distinctive: line 17
Type safety: All data models are fully typed (TypeScript interfaces/types)
- Distinctive: line 26
Do not create a separate page for every query variation or "fan-out" query.
- Distinctive: line 27
Keep a clear, browseable hierarchy (category > page) so pages are not just "closer to search results" than to a site structure.
- Distinctive: line 39
Create TypeScript interfaces for each page type using a two-tier model.
- Distinctive: line 83
All functions must be:
- Distinctive: line 102
No duplicate slugs exist
- Distinctive: line 103
All required fields are present and non-empty
- Distinctive: line 105
All category references resolve to valid categories
- Distinctive: line 106
No orphan pages (pages not reachable through any category)
- Very specific: line 112
JSON files: Create a data/ directory with typed JSON, a loader, and build-time validation.
- Distinctive: line 114
CMS (headless): Create API client with typed responses, implement caching, handle pagination for 1000+ items.
- Distinctive: line 116
Database: Create a query layer with connection pooling, implement cursor-based pagination, add query caching.
- Distinctive: line 120
API: Create a typed API client, implement rate limiting and retry logic, add response caching.
- Very specific: line 127
Put the data layer wrappers in lib/data/index.ts (getPageData, getPagesByCategory, ...) behind "use cache" so page templates stay …
- Distinctive: line 131
Do not prerender the full set at build.
- Very specific: line 132
Check data existence before any Suspense boundary or loading.tsx (status codes: see pseo-templates section 6a).
- Distinctive: line 138
Keep HTML well under the Googlebot 2MB limit (see pseo-templates section 6b).
- Very specific: line 140
Never inline the whole dataset into the page as JSON (for example a full table of 5,000 rows in a <script> tag or serialized props).
- Distinctive: line 184
If using GraphQL, only request index fields in list queries, full fields in single-item queries
- Distinctive: line 201
Before considering this complete:
- Very specific: line 228
Run the validation script (via source.ts) with zero errors.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
pSEO Data Architecture
Design and implement the structured data layer that feeds all programmatic SEO pages. This is the foundation every other pSEO skill depends on.
Core Principles
- Single source of truth: All page data flows from one data layer
- SEO-complete models: Every content model includes all fields needed for metadata, schema markup, and linking
- Unique slugs by construction: Slug generation enforces uniqueness at the data level
- Type safety: All data models are fully typed (TypeScript interfaces/types)
- Separation of concerns: Data fetching is decoupled from page rendering
Data Sufficiency First
Decide which pages deserve to exist before designing anything else. Google's spam policies (scaled content abuse, doorway abuse) target many near-identical pages built mainly to rank. For every planned page type:
- Define the minimum unique data fields a page needs to be useful on its own (real facts, numbers, attributes that differ per page, not just a swapped keyword).
- Score each record against that minimum. Records below the threshold get no page: do not generate them, or return a real 404. Do not ship thin pages as
noindexand plan to flip them later (a noindexed thin page still costs crawl budget and is a maintenance trap). - Do not create a separate page for every query variation or "fan-out" query. One page per distinct entity or intent.
- Keep a clear, browseable hierarchy (category > page) so pages are not just "closer to search results" than to a site structure.
See pseo-scale for sufficiency scoring code.
Scope parameter
$ARGUMENTS source picks section 5; the optional scope limits work: models = section 1, fetching = section 2, slugs = section 3, validation = section 4, nextjs = the Next.js 16.4 section, all (default) = everything.
Implementation Steps
1. Define Content Models
Create TypeScript interfaces for each page type using a two-tier model. The lightweight index tier is safe to hold in memory for all pages; the heavy full tier is loaded per-page only.
// Index tier: safe to load all at once (~1KB per page)
interface PageIndex {
slug: string; // unique, URL-safe
title: string; // page title (50-60 chars target)
metaDescription: string; // meta description (150-160 chars target)
h1: string; // primary heading (can differ from title)
canonicalPath: string; // canonical URL path
category: string; // for hub-spoke and breadcrumbs
lastModified: string; // ISO date for sitemap
}
// Full tier: extends PageIndex with heavy fields (~50-500KB per page)
interface BaseSEOContent extends PageIndex {
introText: string;
bodyContent: string;
faqs?: FAQ[]; // optional visible Q&A; FAQ rich results no longer exist in Google
relatedSlugs?: string[];
featuredImage?: SEOImage;
}Extend BaseSEOContent for each page type with domain-specific fields. The interfaces above show the minimum required fields. See references/content-models.md for the full definitions (which add subcategory, tags, publishedDate, status, and more) and extended type examples (LocationPage, ProductPage, ComparisonPage, CategoryPage).
2. Build the Data-Fetching Layer
Split the layer in two so scripts can use it without Next.js:
lib/data/source.ts: pure fetchers (read JSON/CMS/DB/API, paginate, validate). Nonext/*imports, noserver-only, no"use cache". Must run under plaintsx.lib/data/index.ts: cached wrappers that callsource.tsand add"use cache",cacheLife()andcacheTag(). Pages and templates import only from here.
Build and validation scripts (sitemap checks, validate-data.ts, sufficiency scoring) import source.ts, never index.ts, so they run outside the Next.js runtime.
Both modules together export:
getAllSlugs()- Returns all valid slugs for static generation. Must handle pagination internally when the data source has 1000+ records (fetch in batches, return the complete list).getPageData(slug)- Returns full content for a single pagegetPagesByCategory(category, opts?)- Returns pages in a category for hub pages. Accept optionallimitandoffsetfor paginated hub pages.getRelatedPages(slug, limit?)- Returns related pages for internal linkinggetAllCategories()- Returns all categories for navigation and hubsgetPageCount()- Returns total page count (useful for sitemap splitting and build diagnostics)
All functions must be:
- Cached or memoized during build to avoid redundant reads (see Next.js 16.4 notes below)
- Typed with explicit return types
- Guarded against missing or malformed data
- Internally paginated when the data source imposes limits (e.g., CMS APIs with 100-item pages). The consumer should never need to handle pagination - the data layer abstracts it.
3. Implement Slug Generation
Design a slug strategy that:
- Produces URL-safe, lowercase, hyphenated strings
- Guarantees uniqueness across the entire dataset
- Is deterministic (same input always produces same slug)
- Includes a collision detection mechanism
- Follows a consistent URL hierarchy (e.g.,
/category/page-slug) - Uses hyphens, never underscores, as word separators (Google URL structure doc, 2026-10-23); strip or convert
_in source values
4. Validate Data Integrity
Build a validation function or script that checks:
- No duplicate slugs exist
- All required fields are present and non-empty
- Title and description lengths are within SEO targets
- All category references resolve to valid categories
- No orphan pages (pages not reachable through any category)
5. Set Up Data Source Integration
Based on the data source ($ARGUMENTS or detected):
JSON files: Create a data/ directory with typed JSON, a loader, and build-time validation.
CMS (headless): Create API client with typed responses, implement caching, handle pagination for 1000+ items.
Database: Create a query layer with connection pooling, implement cursor-based pagination, add query caching.
MDX files: Set up frontmatter schema validation, create a content loader with gray-matter parsing.
API: Create a typed API client, implement rate limiting and retry logic, add response caching.
Next.js 16.4 Data Fetching
Applies when the project uses Next.js 16.4.x (>= 16.4.1 for security fixes: two critical unauthenticated RCEs, in use cache with Cache Components and in image optimization with SVG, were fixed 2026-10-14). Cache Components is recommended for all apps in 16.4 and becomes the default in Next.js 17.
- Enable with
cacheComponents: true. Caching is opt-in: mark data functions with the"use cache"directive and set lifetime withcacheLife()/ tag withcacheTag(). Dynamic is the default, so uncached reads in a page make it dynamic. - Put the data layer wrappers in
lib/data/index.ts(getPageData,getPagesByCategory, ...) behind"use cache"so page templates stay prerenderable. The uncached fetchers live inlib/data/source.ts(section 2). paramsandsearchParamsare async:const { slug } = await params.generateStaticParamsmust return at least one param when Cache Components is on (an empty array is a build error). Return a small seed set (top pages by sufficiency score) and let the rest render on demand and cache; usenotFound()for unknown slugs.export const ensureStatic = 'navigation' | 'prefetch' | 'shell'requirescacheComponents. Only'navigation'adds build validation (it fails the build on dynamic content) and it requiresgenerateStaticParamsto return every param.'shell'and'prefetch'add no build validation. With a seeded param set (rest on demand), do not rely onensureStaticto validate anything.- Do not prerender the full set at build. On Vercel a build is capped at 45 minutes per deployment, so seed the top pages and use ISR/on-demand rendering for the long tail.
- Check data existence before any Suspense boundary or
loading.tsx(status codes: see pseo-templates section 6a). - Revalidation:
revalidateTag(tag, profile)now requires a cache-life profile; useupdateTag()in Server Actions for read-your-writes. - Without Cache Components, use
generateStaticParamsreturning[]ordynamic = 'force-static'for runtime ISR, anddynamicParams = falseto 404 unlisted params.
HTML Size Budget
Keep HTML well under the Googlebot 2MB limit (see pseo-templates section 6b). Data-side rules:
- Never inline the whole dataset into the page as JSON (for example a full table of 5,000 rows in a
<script>tag or serialized props). Pass only what the visible page needs. - Watch the RSC/
__next_fpayload and__NEXT_DATA__size: they count toward the 2MB. Keep per-page props to the fields rendered. - Paginate or lazy-load long tables, with each page at its own crawlable URL and canonical (no
#page numbers). - Measure uncompressed HTML size of sample pages in the build and fail above a budget (for example 1MB).
Scale Limits
The in-memory and file-based patterns in this skill work up to ~10K pages. Beyond that:
- 10K-50K pages: Requires a database (PostgreSQL, MySQL). In-memory index tier becomes borderline at 50K (~50MB). File-based data sources are too slow.
- 50K-100K+ pages: Requires database + cache layer (Redis) + cursor-based pagination.
getAllSlugs()must use cursor iteration, not array return. Data sufficiency gating prevents generating thin pages.
See pseo-scale for the complete database-backed data layer, sufficiency scoring, and scale-specific patterns.
Memory-Conscious Data Patterns
At 1000+ pages, how data is loaded matters more than what is loaded. A full content model with body text, FAQs, and images can be 50-500KB per page. Loading all pages into memory simultaneously will OOM.
Two-tier data model:
Split the data layer into lightweight index data and full page data. The PageIndex and BaseSEOContent interfaces from section 1 define the two tiers:
getAllSlugs(),getRelatedPages(),getPagesByCategory()- returnPageIndex[](lightweight, ~1KB per page)getPageData()- returnsBaseSEOContent(or an extended type) for a single page (heavy, ~50-500KB per page, only one at a time)
Never do this:
// Loads ALL full content into memory - will OOM at scale
const allPages = await Promise.all(slugs.map(s => getPageData(s)));Instead:
// Process pages one at a time or in small batches
for (const slug of slugs) {
const page = await getPageData(slug);
await processPage(page);
// page is GC'd after each iteration
}CMS/API pagination:
- Fetch in batches of 100-250 records
- Yield or push to an array incrementally - don't hold all API responses in memory simultaneously
- If using GraphQL, only request index fields in list queries, full fields in single-item queries
File Organization
lib/
data/
index.ts # cached wrappers ("use cache", cacheLife, cacheTag); public API for pages
source.ts # pure fetchers, no next/* or server-only; imported by scripts
types.ts # TypeScript interfaces
slugs.ts # slug generation and validation
validation.ts # data integrity checks
cache.ts # build-time caching utilitiesQuality Checks
Before considering this complete:
- All content models extend BaseSEOContent (which extends PageIndex)
getAllSlugs()returns 0 duplicates- Data validation passes with zero errors
- Data layer exports are fully typed with no
any - Fetching is memoized for build performance
- A test or script can validate the full dataset
- Two-tier data model implemented (index data vs. full page data)
- No function loads all full page content into memory simultaneously
- CMS/API fetching uses batched pagination internally
- Data sufficiency threshold defined per page type; records below it produce no page
- No dataset-sized inline JSON; sample page HTML stays well under 2MB uncompressed
source.tshas nonext/*orserver-onlyimports and runs under plaintsx; scripts import it, notindex.ts- Slugs use hyphens, no underscores
Relationship to Other Skills
This skill provides the data foundation for:
- pseo-templates: Consumes
getPageData()andgetAllSlugs() - pseo-metadata: Reads title, description, canonical from content models
- pseo-schema: Uses structured fields for JSON-LD generation
- gsc: Post-launch monitoring; per-template indexing results feed back into sufficiency thresholds
- pseo-linking: Uses
getRelatedPages()and category data - pseo-quality-guard: Validates against the content models
Verify
Run the validation script (via source.ts) with zero errors. Then build, start the app, and run seo-regression-guard against localhost (npx -y tsx <seo-regression-guard-dir>/scripts/seo-check.ts --site http://localhost:3000). Zero errors for the touched templates before calling this done.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

