Showing preview only (1,899K chars total). Download the full file or copy to clipboard to get everything.
Repository: garmeeh/next-seo
Branch: main
Commit: f74b86bbeaa4
Files: 286
Total size: 1.8 MB
Directory structure:
gitextract_boozz7yy/
├── .changeset/
│ ├── README.md
│ └── config.json
├── .editorconfig
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ ├── dependabot.yml
│ ├── pull_request_template.md
│ └── workflows/
│ ├── changeset-check.yml
│ ├── changesets.yml
│ ├── ci.yml
│ └── release.yml
├── .gitignore
├── .husky/
│ └── pre-commit
├── .npmignore
├── .prettierignore
├── .vscode/
│ └── settings.json
├── ADDING_NEW_COMPONENTS.md
├── AGENTS.md
├── CHANGELOG.md
├── CLAUDE.md
├── CONTRIBUTING.md
├── CUSTOM_COMPONENTS.md
├── LICENSE
├── LICENSE.md
├── LIST.md
├── README.md
├── eslint.config.mjs
├── examples/
│ └── app-router-showcase/
│ ├── .gitignore
│ ├── CLAUDE.md
│ ├── README.md
│ ├── app/
│ │ ├── aggregate-rating/
│ │ │ └── page.tsx
│ │ ├── aggregate-rating-restaurant/
│ │ │ └── page.tsx
│ │ ├── article/
│ │ │ └── page.tsx
│ │ ├── blog-posting/
│ │ │ └── page.tsx
│ │ ├── breadcrumb/
│ │ │ ├── advanced/
│ │ │ │ └── page.tsx
│ │ │ ├── multiple/
│ │ │ │ └── page.tsx
│ │ │ └── page.tsx
│ │ ├── carousel-course/
│ │ │ └── page.tsx
│ │ ├── carousel-movie/
│ │ │ └── page.tsx
│ │ ├── carousel-recipe/
│ │ │ └── page.tsx
│ │ ├── carousel-restaurant/
│ │ │ └── page.tsx
│ │ ├── carousel-summary/
│ │ │ └── page.tsx
│ │ ├── claim-review/
│ │ │ └── page.tsx
│ │ ├── claim-review-advanced/
│ │ │ └── page.tsx
│ │ ├── claim-review-organization/
│ │ │ └── page.tsx
│ │ ├── course/
│ │ │ └── page.tsx
│ │ ├── course-list/
│ │ │ └── page.tsx
│ │ ├── course-list-summary/
│ │ │ └── page.tsx
│ │ ├── creative-work/
│ │ │ └── page.tsx
│ │ ├── creative-work-blog/
│ │ │ └── page.tsx
│ │ ├── creative-work-multiple/
│ │ │ └── page.tsx
│ │ ├── creative-work-news/
│ │ │ └── page.tsx
│ │ ├── custom-podcast/
│ │ │ └── page.tsx
│ │ ├── custom-service/
│ │ │ └── page.tsx
│ │ ├── dataset/
│ │ │ └── page.tsx
│ │ ├── dataset-advanced/
│ │ │ └── page.tsx
│ │ ├── dataset-catalog/
│ │ │ └── page.tsx
│ │ ├── dataset-nested/
│ │ │ └── page.tsx
│ │ ├── discussion-forum/
│ │ │ └── page.tsx
│ │ ├── discussion-forum-advanced/
│ │ │ └── page.tsx
│ │ ├── discussion-forum-deleted/
│ │ │ └── page.tsx
│ │ ├── employer-aggregate-rating/
│ │ │ └── page.tsx
│ │ ├── employer-aggregate-rating-advanced/
│ │ │ └── page.tsx
│ │ ├── employer-aggregate-rating-custom-scale/
│ │ │ └── page.tsx
│ │ ├── event/
│ │ │ └── page.tsx
│ │ ├── event-cancelled/
│ │ │ └── page.tsx
│ │ ├── event-free/
│ │ │ └── page.tsx
│ │ ├── event-rescheduled/
│ │ │ └── page.tsx
│ │ ├── faq/
│ │ │ └── page.tsx
│ │ ├── faq-advanced/
│ │ │ └── page.tsx
│ │ ├── faq-health/
│ │ │ └── page.tsx
│ │ ├── globals.css
│ │ ├── howto/
│ │ │ └── page.tsx
│ │ ├── howto-advanced/
│ │ │ └── page.tsx
│ │ ├── image/
│ │ │ └── page.tsx
│ │ ├── image-advanced/
│ │ │ └── page.tsx
│ │ ├── image-multiple/
│ │ │ └── page.tsx
│ │ ├── job-posting/
│ │ │ └── page.tsx
│ │ ├── job-posting-advanced/
│ │ │ └── page.tsx
│ │ ├── job-posting-remote/
│ │ │ └── page.tsx
│ │ ├── jsonld-test-page/
│ │ │ └── page.tsx
│ │ ├── layout.tsx
│ │ ├── local-business/
│ │ │ └── page.tsx
│ │ ├── merchant-return-policy/
│ │ │ └── page.tsx
│ │ ├── merchant-return-policy-advanced/
│ │ │ └── page.tsx
│ │ ├── merchant-return-policy-link/
│ │ │ └── page.tsx
│ │ ├── mobile-app/
│ │ │ └── page.tsx
│ │ ├── movie-carousel/
│ │ │ └── page.tsx
│ │ ├── movie-carousel-advanced/
│ │ │ └── page.tsx
│ │ ├── movie-carousel-summary/
│ │ │ └── page.tsx
│ │ ├── news-article/
│ │ │ └── page.tsx
│ │ ├── online-store/
│ │ │ └── page.tsx
│ │ ├── online-store-loyalty/
│ │ │ └── page.tsx
│ │ ├── organization/
│ │ │ └── page.tsx
│ │ ├── organization-advanced/
│ │ │ └── page.tsx
│ │ ├── organization-reviews/
│ │ │ └── page.tsx
│ │ ├── page.module.css
│ │ ├── page.tsx
│ │ ├── product/
│ │ │ └── page.tsx
│ │ ├── product-3d-model/
│ │ │ └── page.tsx
│ │ ├── product-aggregate/
│ │ │ └── page.tsx
│ │ ├── product-certification/
│ │ │ └── page.tsx
│ │ ├── product-member-pricing/
│ │ │ └── page.tsx
│ │ ├── product-review/
│ │ │ └── page.tsx
│ │ ├── product-sale-pricing/
│ │ │ └── page.tsx
│ │ ├── product-shipping-options/
│ │ │ └── page.tsx
│ │ ├── product-unit-pricing/
│ │ │ └── page.tsx
│ │ ├── product-variants/
│ │ │ └── page.tsx
│ │ ├── product-variants-advanced/
│ │ │ └── page.tsx
│ │ ├── product-variants-multipage/
│ │ │ └── page.tsx
│ │ ├── product-with-return-policy/
│ │ │ └── page.tsx
│ │ ├── profile/
│ │ │ └── page.tsx
│ │ ├── profile-advanced/
│ │ │ └── page.tsx
│ │ ├── profile-organization/
│ │ │ └── page.tsx
│ │ ├── quiz/
│ │ │ └── page.tsx
│ │ ├── quiz-advanced/
│ │ │ └── page.tsx
│ │ ├── quiz-biology/
│ │ │ └── page.tsx
│ │ ├── recipe/
│ │ │ └── page.tsx
│ │ ├── recipe-advanced/
│ │ │ └── page.tsx
│ │ ├── restaurant/
│ │ │ └── page.tsx
│ │ ├── review/
│ │ │ └── page.tsx
│ │ ├── review-advanced/
│ │ │ └── page.tsx
│ │ ├── review-movie/
│ │ │ └── page.tsx
│ │ ├── social-media-posting/
│ │ │ └── page.tsx
│ │ ├── software-app/
│ │ │ └── page.tsx
│ │ ├── software-app-paid/
│ │ │ └── page.tsx
│ │ ├── store-with-departments/
│ │ │ └── page.tsx
│ │ ├── test-arrays/
│ │ │ └── page.tsx
│ │ ├── test-nested/
│ │ │ └── page.tsx
│ │ ├── test-url-params/
│ │ │ └── page.tsx
│ │ ├── vacation-rental/
│ │ │ └── page.tsx
│ │ ├── vacation-rental-advanced/
│ │ │ └── page.tsx
│ │ ├── vacation-rental-apartment/
│ │ │ └── page.tsx
│ │ ├── video/
│ │ │ └── page.tsx
│ │ ├── video-advanced/
│ │ │ └── page.tsx
│ │ ├── video-clips/
│ │ │ └── page.tsx
│ │ ├── video-game/
│ │ │ └── page.tsx
│ │ ├── video-live/
│ │ │ └── page.tsx
│ │ ├── video-seekto/
│ │ │ └── page.tsx
│ │ └── web-app/
│ │ └── page.tsx
│ ├── components/
│ │ └── custom/
│ │ ├── PodcastSeriesJsonLd.tsx
│ │ └── ServiceJsonLd.tsx
│ ├── eslint.config.mjs
│ ├── next.config.ts
│ ├── package.json
│ └── tsconfig.json
├── package.json
├── playwright.config.ts
├── pnpm-workspace.yaml
├── repomix.config.json
├── src/
│ ├── components/
│ │ ├── .gitkeep
│ │ ├── AggregateRatingJsonLd.test.tsx
│ │ ├── AggregateRatingJsonLd.tsx
│ │ ├── ArticleJsonLd.test.tsx
│ │ ├── ArticleJsonLd.tsx
│ │ ├── BreadcrumbJsonLd.test.tsx
│ │ ├── BreadcrumbJsonLd.tsx
│ │ ├── CLAUDE.md
│ │ ├── CarouselJsonLd.test.tsx
│ │ ├── CarouselJsonLd.tsx
│ │ ├── ClaimReviewJsonLd.test.tsx
│ │ ├── ClaimReviewJsonLd.tsx
│ │ ├── CourseJsonLd.test.tsx
│ │ ├── CourseJsonLd.tsx
│ │ ├── CreativeWorkJsonLd.test.tsx
│ │ ├── CreativeWorkJsonLd.tsx
│ │ ├── DatasetJsonLd.test.tsx
│ │ ├── DatasetJsonLd.tsx
│ │ ├── DiscussionForumPostingJsonLd.test.tsx
│ │ ├── DiscussionForumPostingJsonLd.tsx
│ │ ├── EmployerAggregateRatingJsonLd.test.tsx
│ │ ├── EmployerAggregateRatingJsonLd.tsx
│ │ ├── EventJsonLd.test.tsx
│ │ ├── EventJsonLd.tsx
│ │ ├── FAQJsonLd.test.tsx
│ │ ├── FAQJsonLd.tsx
│ │ ├── HowToJsonLd.test.tsx
│ │ ├── HowToJsonLd.tsx
│ │ ├── ImageJsonLd.test.tsx
│ │ ├── ImageJsonLd.tsx
│ │ ├── JobPostingJsonLd.test.tsx
│ │ ├── JobPostingJsonLd.tsx
│ │ ├── LocalBusinessJsonLd.test.tsx
│ │ ├── LocalBusinessJsonLd.tsx
│ │ ├── MerchantReturnPolicyJsonLd.test.tsx
│ │ ├── MerchantReturnPolicyJsonLd.tsx
│ │ ├── MovieCarouselJsonLd.test.tsx
│ │ ├── MovieCarouselJsonLd.tsx
│ │ ├── OrganizationJsonLd.test.tsx
│ │ ├── OrganizationJsonLd.tsx
│ │ ├── ProductJsonLd.test.tsx
│ │ ├── ProductJsonLd.tsx
│ │ ├── ProfilePageJsonLd.test.tsx
│ │ ├── ProfilePageJsonLd.tsx
│ │ ├── QuizJsonLd.test.tsx
│ │ ├── QuizJsonLd.tsx
│ │ ├── RecipeJsonLd.test.tsx
│ │ ├── RecipeJsonLd.tsx
│ │ ├── ReviewJsonLd.test.tsx
│ │ ├── ReviewJsonLd.tsx
│ │ ├── SoftwareApplicationJsonLd.test.tsx
│ │ ├── SoftwareApplicationJsonLd.tsx
│ │ ├── VacationRentalJsonLd.test.tsx
│ │ ├── VacationRentalJsonLd.tsx
│ │ ├── VideoJsonLd.test.tsx
│ │ └── VideoJsonLd.tsx
│ ├── core/
│ │ ├── JsonLdScript.test.tsx
│ │ └── JsonLdScript.tsx
│ ├── index.ts
│ ├── pages/
│ │ ├── README.md
│ │ ├── core/
│ │ │ ├── __snapshots__/
│ │ │ │ └── buildTags.test.tsx.snap
│ │ │ ├── buildTags.test.tsx
│ │ │ └── buildTags.tsx
│ │ ├── index.ts
│ │ ├── types/
│ │ │ └── index.ts
│ │ └── utils/
│ │ └── processors.ts
│ ├── types/
│ │ ├── article.types.ts
│ │ ├── breadcrumb.types.ts
│ │ ├── carousel.types.ts
│ │ ├── claimreview.types.ts
│ │ ├── common.types.ts
│ │ ├── course.types.ts
│ │ ├── creativework.types.ts
│ │ ├── dataset.types.ts
│ │ ├── discussionforum.types.ts
│ │ ├── employer-aggregate-rating.types.ts
│ │ ├── event.types.ts
│ │ ├── faq.types.ts
│ │ ├── howto.types.ts
│ │ ├── image.types.ts
│ │ ├── index.ts
│ │ ├── jobposting.types.ts
│ │ ├── localbusiness.types.ts
│ │ ├── merchantreturnpolicy.types.ts
│ │ ├── movie-carousel.types.ts
│ │ ├── organization.types.ts
│ │ ├── product.types.ts
│ │ ├── profile.types.ts
│ │ ├── quiz.types.ts
│ │ ├── recipe.types.ts
│ │ ├── review.types.ts
│ │ ├── softwareApplication.types.ts
│ │ ├── vacationrental.types.ts
│ │ └── video.types.ts
│ └── utils/
│ ├── processors.export.ts
│ ├── processors.test.ts
│ ├── processors.ts
│ ├── stringify.test.ts
│ └── stringify.ts
├── tests/
│ ├── e2e/
│ │ ├── .gitkeep
│ │ ├── CLAUDE.md
│ │ ├── aggregateRatingJsonLd.e2e.spec.ts
│ │ ├── articleJsonLd.e2e.spec.ts
│ │ ├── breadcrumbJsonLd.e2e.spec.ts
│ │ ├── carouselJsonLd.e2e.spec.ts
│ │ ├── claimReviewJsonLd.e2e.spec.ts
│ │ ├── courseJsonLd.e2e.spec.ts
│ │ ├── creativeWorkJsonLd.e2e.spec.ts
│ │ ├── customComponents.e2e.spec.ts
│ │ ├── datasetJsonLd.e2e.spec.ts
│ │ ├── discussionForumPostingJsonLd.e2e.spec.ts
│ │ ├── employerAggregateRatingJsonLd.e2e.spec.ts
│ │ ├── eventJsonLd.e2e.spec.ts
│ │ ├── faqJsonLd.e2e.spec.ts
│ │ ├── howtoJsonLd.e2e.spec.ts
│ │ ├── imageJsonLd.e2e.spec.ts
│ │ ├── jobPostingJsonLd.e2e.spec.ts
│ │ ├── jsonLdScript.e2e.spec.ts
│ │ ├── jsonValidation.e2e.spec.ts
│ │ ├── localBusinessJsonLd.e2e.spec.ts
│ │ ├── merchantReturnPolicyJsonLd.e2e.spec.ts
│ │ ├── movieCarouselJsonLd.e2e.spec.ts
│ │ ├── organizationJsonLd.e2e.spec.ts
│ │ ├── productJsonLd.e2e.spec.ts
│ │ ├── profilePageJsonLd.e2e.spec.ts
│ │ ├── quizJsonLd.e2e.spec.ts
│ │ ├── recipeJsonLd.e2e.spec.ts
│ │ ├── reviewJsonLd.e2e.spec.ts
│ │ ├── security.e2e.spec.ts
│ │ ├── softwareApplicationJsonLd.e2e.spec.ts
│ │ ├── vacationRentalJsonLd.e2e.spec.ts
│ │ └── videoJsonLd.e2e.spec.ts
│ └── unit/
│ └── setup.ts
├── tsconfig.json
├── tsup.config.ts
└── vitest.config.ts
================================================
FILE CONTENTS
================================================
================================================
FILE: .changeset/README.md
================================================
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
================================================
FILE: .changeset/config.json
================================================
{
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["app-router-showcase"]
}
================================================
FILE: .editorconfig
================================================
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ""
labels: ""
assignees: ""
---
**Describe the bug**
A clear and concise description of what the bug is.
**Reproduction**
For issues to be triaged in a timely manner please provide a Codesandbox/Github of the issue in it's simplest reproduction.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ""
labels: ""
assignees: ""
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
Any links to Google or http://schema.org/ to support to validity in terms of SEO will be a great help.
================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
commit-message:
prefix: "chore(deps)"
open-pull-requests-limit: 5
================================================
FILE: .github/pull_request_template.md
================================================
================================================
FILE: .github/workflows/changeset-check.yml
================================================
name: Changeset Check
on:
pull_request:
types: [opened, synchronize]
jobs:
changeset-check:
name: Check for Changeset
runs-on: ubuntu-latest
if: github.event.pull_request.user.login != 'dependabot[bot]' && github.event.pull_request.user.login != 'github-actions[bot]'
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Check for code changes
id: code-changes
run: |
# Get list of changed files
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}..HEAD)
# Check if any code files were changed (not just docs/config)
CODE_CHANGED=false
for file in $CHANGED_FILES; do
# Check if file is a code file (not docs, config, or github workflows)
if [[ "$file" =~ \.(ts|tsx|js|jsx)$ ]] && [[ ! "$file" =~ ^\.github/ ]] && [[ ! "$file" =~ \.(md|mdx)$ ]]; then
CODE_CHANGED=true
break
fi
done
echo "code_changed=$CODE_CHANGED" >> $GITHUB_OUTPUT
- name: Check for changeset
if: steps.code-changes.outputs.code_changed == 'true'
run: |
pnpm changeset status --since=origin/${{ github.base_ref }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Comment on PR if changeset is missing
if: failure() && steps.code-changes.outputs.code_changed == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const body = `## 📝 Changeset Required
This PR includes code changes but is missing a changeset. Changesets help us:
- Track changes for release notes
- Determine version bumps (patch/minor/major)
- Credit contributors properly
### How to add a changeset:
1. Run \`pnpm changeset\` in your local environment
2. Select the type of change (patch/minor/major)
3. Write a brief description of your changes
4. Commit the generated changeset file
### Change types:
- **patch**: Bug fixes, internal changes (0.0.X)
- **minor**: New features, non-breaking changes (0.X.0)
- **major**: Breaking changes (X.0.0)
### Example:
\`\`\`bash
$ pnpm changeset
? What kind of change is this? › patch
? Summary › Fixed TypeScript types for ArticleJsonLd component
\`\`\`
If this PR only contains documentation or non-code changes, you can ignore this message.`;
// Check if we already commented
const comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const hasComment = comments.data.some(comment =>
comment.body.includes('## 📝 Changeset Required')
);
if (!hasComment) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body
});
}
- name: Skip message for non-code changes
if: steps.code-changes.outputs.code_changed == 'false'
run: echo "No code changes detected, changeset not required."
================================================
FILE: .github/workflows/changesets.yml
================================================
name: Changesets
on:
push:
branches:
- main
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
pull-requests: write
id-token: write
jobs:
build:
name: Build Library
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build library
run: pnpm build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
retention-days: 1
lint:
name: Lint Library
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run ESLint
run: pnpm lint
typecheck:
name: Type Check Library
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run TypeScript type checking
run: pnpm typecheck
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run unit tests
run: pnpm test:unit
example-lint:
name: Lint Example App
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint example app
run: pnpm example:lint
example-typecheck:
name: Type Check Example App
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Type check example app
run: pnpm example:typecheck
e2e-tests:
name: E2E Tests
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Build example app
run: pnpm example:build
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run E2E tests
run: pnpm test:e2e --project=chromium
env:
CI: true
release:
name: Create Release PR or Publish
runs-on: ubuntu-latest
needs:
[
build,
lint,
typecheck,
unit-tests,
example-lint,
example-typecheck,
e2e-tests,
]
outputs:
published: ${{ steps.changesets.outputs.published }}
publishedPackages: ${{ steps.changesets.outputs.publishedPackages }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Create Release Pull Request or Publish
id: changesets
uses: changesets/action@v1
with:
title: "Release: Version Packages"
commit: "chore: version packages"
publish: pnpm release
createGithubReleases: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
post-release:
name: Post Release Actions
runs-on: ubuntu-latest
needs: release
if: needs.release.outputs.published == 'true'
steps:
- name: Report Released Packages
run: |
echo "🎉 Released packages:"
echo "${{ needs.release.outputs.publishedPackages }}"
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
pull_request:
branches: [main]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build Library
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build library
run: pnpm build
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: dist
path: dist
retention-days: 1
lint:
name: Lint Library
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run ESLint
run: pnpm lint
typecheck:
name: Type Check Library
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run TypeScript type checking
run: pnpm typecheck
unit-tests:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Run unit tests
run: pnpm test:unit
- name: Upload coverage reports
uses: actions/upload-artifact@v4
if: always()
with:
name: coverage-report
path: coverage/
retention-days: 7
example-lint:
name: Lint Example App
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Lint example app
run: pnpm example:lint
example-typecheck:
name: Type Check Example App
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Type check example app
run: pnpm example:typecheck
e2e-tests:
name: E2E Tests
runs-on: ubuntu-latest
needs: build
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: dist
path: dist
- name: Build example app
run: pnpm example:build
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run E2E tests
run: pnpm test:e2e --project=chromium
env:
CI: true
- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
all-checks:
name: All CI Checks
runs-on: ubuntu-latest
needs:
[
build,
lint,
typecheck,
unit-tests,
example-lint,
example-typecheck,
e2e-tests,
]
if: always()
steps:
- name: Check all job statuses
run: |
if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
echo "One or more CI checks failed"
exit 1
elif [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
echo "One or more CI checks were cancelled"
exit 1
else
echo "All CI checks passed successfully"
fi
================================================
FILE: .github/workflows/release.yml
================================================
name: Manual Release
on:
workflow_dispatch:
inputs:
version:
description: "Version to release (e.g., 7.1.0)"
required: true
type: string
tag:
description: "NPM tag (latest, next, alpha, beta)"
required: true
type: choice
default: "latest"
options:
- latest
- next
- alpha
- beta
permissions:
contents: write
id-token: write
jobs:
manual-release:
name: Manual Release
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Update package version
run: |
npm version ${{ github.event.inputs.version }} --no-git-tag-version
echo "Updated package.json to version ${{ github.event.inputs.version }}"
- name: Build package
run: pnpm build
- name: Run TypeScript type checking
run: pnpm typecheck
- name: Run linting
run: pnpm lint
- name: Run unit tests
run: pnpm test:unit
- name: Lint example app
run: pnpm example:lint
- name: Type check example app
run: pnpm example:typecheck
- name: Build example app
run: pnpm example:build
- name: Install Playwright browsers
run: npx playwright install --with-deps chromium
- name: Run E2E tests
run: pnpm test:e2e --project=chromium
env:
CI: true
- name: Publish to npm
run: |
echo "Publishing version ${{ github.event.inputs.version }} with tag '${{ github.event.inputs.tag }}'"
pnpm publish --tag ${{ github.event.inputs.tag }} --no-git-checks
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create Git tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag -a "v${{ github.event.inputs.version }}" -m "Release v${{ github.event.inputs.version }}"
git push origin "v${{ github.event.inputs.version }}"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: "v${{ github.event.inputs.version }}"
name: "v${{ github.event.inputs.version }}"
body: |
## Manual Release v${{ github.event.inputs.version }}
Published to npm with tag: `${{ github.event.inputs.tag }}`
Install with:
```bash
npm install next-seo@${{ github.event.inputs.tag }}
# or
pnpm add next-seo@${{ github.event.inputs.tag }}
```
prerelease: ${{ github.event.inputs.tag != 'latest' }}
generate_release_notes: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# Dependencies
node_modules
/.pnp
.pnp.js
# Testing
coverage
# Next.js
.next/
build/
out/
# TypeScript
*.tsbuildinfo
# Misc
.DS_Store
*.pem
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.sublime-workspace
# Husky
.husky/_/
# SWC
.swc/
*.log
.history
dist
cypress/videos
cypress/screenshots
package-lock.json
lib
notes.md
playwright-report
repomix-output.xml
.last-run.json
.claude
tasks/*
test-results/*
task.md
local_docs/*
================================================
FILE: .husky/pre-commit
================================================
pnpm lint-staged
================================================
FILE: .npmignore
================================================
# Source code
src/
examples/
# Config files
.husky/
.github/
.vscode/
.editorconfig
.prettierignore
commitlint.config.js
eslint.config.mjs
playwright.config.ts
tsconfig.json
tsup.config.ts
vitest.config.ts
pnpm-workspace.yaml
repomix.config.json
# Documentation
*.md
!README.md
# Test files
tests/
test-results/
coverage/
**/*.test.*
**/*.spec.*
# Development files
tasks/
schemas/
# Build artifacts that shouldn't be published
*.log
.DS_Store
================================================
FILE: .prettierignore
================================================
node_modules
.next
.swc
coverage
dist
build
package.json
pnpm-lock.yaml
# Ignore example project built files if any
examples/app-router-showcase/.next
examples/app-router-showcase/out
================================================
FILE: .vscode/settings.json
================================================
{
"cSpell.words": ["noindex", "nofollow"],
"typescript.tsdk": "node_modules/typescript/lib"
}
================================================
FILE: ADDING_NEW_COMPONENTS.md
================================================
# Adding New Components to Next SEO
This guide walks through the process of adding new JSON-LD structured data components to next-seo. We'll use the ArticleJsonLd component as a reference implementation.
## Table of Contents
1. [Research Phase](#1-research-phase)
2. [Type Definitions](#2-type-definitions)
3. [Component Implementation](#3-component-implementation)
4. [Export Configuration](#4-export-configuration)
5. [Unit Tests](#5-unit-tests)
6. [Documentation](#6-documentation)
7. [Example Pages](#7-example-pages)
8. [E2E Tests](#8-e2e-tests)
9. [Final Verification](#9-final-verification)
## 1. Research Phase
Before implementing, thoroughly research the structured data specification:
1. **Visit Google's Documentation**
- Go to [Google's Structured Data Gallery](https://developers.google.com/search/docs/appearance/structured-data/search-gallery)
- Find the specific type you're implementing (e.g., Article, Product, Recipe)
- Note all required and recommended properties
2. **Analyze Schema Types**
- Identify all subtypes (e.g., Article has NewsArticle, BlogPosting, Blog)
- Note property variations between types
- Check for special formatting requirements (dates, images, etc.)
3. **Review Existing Implementation**
- If updating from an older version, fetch the previous implementation
- Identify any missing features or properties
- Ensure backward compatibility where possible
## 2. Type Definitions
Create comprehensive TypeScript types in `src/types/[component].types.ts`:
```typescript
// src/types/article.types.ts
import type { ImageObject, Person, Organization, Author } from "./common.types";
// Note: Common types like ImageObject, Person, Organization, and Author
// are now defined in common.types.ts to avoid duplication
// Base interface with common properties
export interface ArticleBase {
headline: string;
url?: string;
author?: Author | Author[];
datePublished?: string;
dateModified?: string;
image?: string | ImageObject | (string | ImageObject)[];
publisher?: Organization;
description?: string;
isAccessibleForFree?: boolean;
mainEntityOfPage?:
| string
| {
"@type": "WebPage";
"@id": string;
};
}
// Specific schema type interfaces
export interface Article extends ArticleBase {
"@type": "Article";
}
export interface NewsArticle extends ArticleBase {
"@type": "NewsArticle";
}
export interface BlogPosting extends ArticleBase {
"@type": "BlogPosting";
}
// Component props type
export type ArticleJsonLdProps = (
| Omit<Article, "@type">
| Omit<NewsArticle, "@type">
| Omit<BlogPosting, "@type">
) & {
type?: "Article" | "NewsArticle" | "BlogPosting";
scriptId?: string;
scriptKey?: string;
};
```
### Key Patterns:
- Use union types for flexible inputs (e.g., `string | Person | Organization`)
- Support both single items and arrays where appropriate
- Extend common interfaces to reduce duplication
- Make all properties optional except truly required ones
- Include component-specific props like `scriptId` and `scriptKey`
- **Important**: Reuse types from `common.types.ts` for shared definitions like `ImageObject`, `Person`, `Organization`, and `Author`
### The @type Optional Pattern
A core design principle of next-seo is that developers should not need to specify `@type` properties manually. This provides better developer experience while maintaining full Schema.org compliance.
#### How It Works:
1. **Type Definitions**: Use `Omit<Type, "@type">` to create props that don't require `@type`:
```typescript
export type ArticleJsonLdProps = (
| Omit<Article, "@type">
| Omit<NewsArticle, "@type">
| Omit<BlogPosting, "@type">
) & {
type?: "Article" | "NewsArticle" | "BlogPosting";
// ... other props
};
```
2. **Process Functions**: Automatically add the correct `@type` based on input:
```typescript
// Developers can pass a simple string
author="John Doe"
// Process function converts it to a proper Person object
processAuthor("John Doe") // → { "@type": "Person", name: "John Doe" }
// Or pass an object without @type
author={{ name: "John Doe", url: "https://example.com" }}
// Process function adds @type intelligently
processAuthor({...}) // → { "@type": "Person", name: "John Doe", url: "..." }
```
3. **Intelligent Type Detection**: Process functions use property analysis to determine types:
- Objects with `logo`, `address`, or `contactPoint` → Organization
- Objects with `familyName` or `givenName` → Person
- Default fallbacks ensure valid Schema.org output
#### Benefits:
- **Less Boilerplate**: Developers don't need to remember Schema.org type names
- **Flexible Input**: Accept strings, objects with/without `@type`
- **Type Safety**: Full TypeScript support throughout
- **Forward Compatible**: Can still accept objects with `@type` if provided
## 3. Component Implementation
Create the component in `src/components/[Component]JsonLd.tsx`:
```typescript
// src/components/ArticleJsonLd.tsx
import { JsonLdScript } from "~/core/JsonLdScript";
import type { ArticleJsonLdProps } from "~/types/article.types";
import { processAuthor, processImage } from "~/utils/processors";
// Note: Common processing functions like processAuthor and processImage
// are now available in ~/utils/processors.ts to avoid duplication
export default function ArticleJsonLd({
type = "Article",
scriptId,
scriptKey,
headline,
url,
author,
datePublished,
dateModified,
image,
publisher,
description,
isAccessibleForFree,
mainEntityOfPage,
}: ArticleJsonLdProps) {
const data = {
"@context": "https://schema.org",
"@type": type,
headline,
...(url && { url }),
...(author && {
author: Array.isArray(author)
? author.map(processAuthor)
: processAuthor(author),
}),
...(datePublished && { datePublished }),
...(dateModified && { dateModified }),
// Apply defaults where appropriate
...(!dateModified && datePublished && { dateModified: datePublished }),
...(image && {
image: Array.isArray(image) ? image.map(processImage) : processImage(image),
}),
...(publisher && { publisher }),
...(description && { description }),
...(isAccessibleForFree !== undefined && { isAccessibleForFree }),
...(mainEntityOfPage && { mainEntityOfPage }),
};
return (
<JsonLdScript
data={data}
id={scriptId}
scriptKey={scriptKey || `article-jsonld-${type}`}
/>
);
}
export type { ArticleJsonLdProps };
```
### Implementation Guidelines:
- Use the existing `JsonLdScript` component for rendering (now with TypeScript generics support)
- Process flexible inputs to proper schema format using shared utilities from `~/utils/processors`
- Use object spread with conditional inclusion for optional properties
- Handle arrays appropriately with `.map()`
- Apply sensible defaults (e.g., dateModified defaults to datePublished)
- Ensure boolean values are explicitly checked with `!== undefined`
- **Always use process functions** for properties that accept flexible types (strings, objects with/without `@type`)
- **Never require developers to specify `@type`** - the component should set the main `@type` from the `type` prop, and process functions should handle nested objects
## 4. Export Configuration
Update `src/index.ts` to export your component:
```typescript
export { JsonLdScript } from "./core/JsonLdScript";
export {
default as ArticleJsonLd,
type ArticleJsonLdProps,
} from "./components/ArticleJsonLd";
// Add your new component here
export const version = "7.0.0-alpha.0";
```
## 5. Unit Tests
Create comprehensive tests in `src/components/[Component]JsonLd.test.tsx`:
```typescript
import { render } from "@testing-library/react";
import { describe, it, expect } from "vitest";
import ArticleJsonLd from "./ArticleJsonLd";
describe("ArticleJsonLd", () => {
it("renders basic Article with minimal props", () => {
const { container } = render(
<ArticleJsonLd
headline="Test Article"
datePublished="2024-01-01T00:00:00.000Z"
/>
);
const script = container.querySelector('script[type="application/ld+json"]');
expect(script).toBeTruthy();
const jsonData = JSON.parse(script!.textContent!);
expect(jsonData).toEqual({
"@context": "https://schema.org",
"@type": "Article",
headline: "Test Article",
datePublished: "2024-01-01T00:00:00.000Z",
dateModified: "2024-01-01T00:00:00.000Z", // defaults to datePublished
});
});
// Test each schema type
it("renders NewsArticle type when specified", () => {
// ... test implementation
});
// Test flexible inputs
it("handles string author", () => {
// ... converts string to Person object
});
it("handles multiple authors", () => {
// ... test array handling
});
// Test all properties
it("handles all optional properties", () => {
// ... comprehensive test with all props
});
// Test edge cases
it("handles isAccessibleForFree as false", () => {
// ... ensure boolean false is included
});
});
```
### Testing Checklist:
- ✅ Basic rendering with minimal props
- ✅ All schema type variations
- ✅ String to object conversions
- ✅ Array handling for authors and images
- ✅ All optional properties
- ✅ Default value application
- ✅ Boolean value handling
- ✅ Custom scriptId and scriptKey
## 6. Documentation
Add comprehensive documentation to `README.md`:
````markdown
### ArticleJsonLd
The `ArticleJsonLd` component helps you add structured data for articles, blog posts, and news articles to improve their appearance in search results.
#### Basic Usage
```tsx
import { ArticleJsonLd } from "next-seo";
<ArticleJsonLd
headline="My Amazing Article"
datePublished="2024-01-01T08:00:00+08:00"
author="John Doe"
image="https://example.com/article-image.jpg"
description="This article explains amazing things"
/>;
```
````
#### Props
| Property | Type | Description |
| ---------- | --------------------------------------------- | ------------------------------------------ |
| `type` | `"Article" \| "NewsArticle" \| "BlogPosting"` | The type of article. Defaults to "Article" |
| `headline` | `string` | **Required.** The headline of the article |
| ... | ... | ... |
#### Best Practices
1. **Always include images**: Google recommends multiple aspect ratios
2. **Use ISO 8601 dates**: Include timezone information
3. **Multiple authors**: List all authors when applicable
````
## 7. Example Pages
Create example pages in `examples/app-router-showcase/app/[component]/page.tsx`:
```tsx
import { ArticleJsonLd } from "next-seo";
export default function ArticlePage() {
return (
<div className="container mx-auto p-8">
<ArticleJsonLd
headline="Understanding Next.js App Router"
url="https://example.com/articles/nextjs-app-router"
datePublished="2024-01-01T08:00:00+00:00"
author="Sarah Johnson"
image="https://example.com/images/nextjs-article.jpg"
description="A comprehensive guide to Next.js App Router"
/>
<article className="prose lg:prose-xl">
<h1>Understanding Next.js App Router</h1>
{/* Article content */}
</article>
</div>
);
}
````
Create examples for:
- Basic usage (minimal props)
- Advanced usage (all features)
- Each schema type variation
## 8. E2E Tests
Create Playwright tests in `tests/e2e/[component]JsonLd.e2e.spec.ts`:
### Important E2E Testing Guidelines
**ALL E2E tests must use real example pages!** E2E tests should test the actual component behavior through real pages in the example app. Never mock or inject content in E2E tests.
❌ **DO NOT** use `page.route()` to inject mock HTML:
```typescript
// BAD - This is not a real E2E test!
await page.route("/test-page", async (route) => {
await route.fulfill({
body: `<html>...</html>`,
});
});
```
✅ **DO** create real example pages and test them:
```typescript
// GOOD - Test real pages with actual components
await page.goto("/article");
```
### Creating E2E Tests
For every E2E test scenario, you must:
1. Create a real example page in `examples/app-router-showcase/app/`
2. Write the E2E test to navigate to that page
3. Test the actual rendered output
```typescript
import { test, expect } from "@playwright/test";
test.describe("ArticleJsonLd", () => {
test("renders basic Article structured data", async ({ page }) => {
// Navigate to the real example page
await page.goto("/article");
const jsonLdScript = await page
.locator('script[type="application/ld+json"]')
.textContent();
expect(jsonLdScript).toBeTruthy();
const jsonData = JSON.parse(jsonLdScript!);
// Verify all properties
expect(jsonData["@context"]).toBe("https://schema.org");
expect(jsonData["@type"]).toBe("Article");
expect(jsonData.headline).toBe("Understanding Next.js App Router");
// ... test all properties
});
test("properly escapes HTML entities in content", async ({ page }) => {
// Navigate to a real example page with special characters
await page.goto("/article-special-chars");
const jsonLdScript = await page
.locator('script[type="application/ld+json"]')
.textContent();
// Verify JSON is valid and content is properly escaped
const jsonData = JSON.parse(jsonLdScript!);
expect(jsonData.headline).toContain("Special & Characters");
// Check that dangerous content is escaped in the raw JSON
expect(jsonLdScript).toContain("\\u003C/script>");
});
});
```
### When to Create Additional Example Pages
Create new example pages for:
- Basic usage with minimal props
- Advanced usage with all features
- Each schema type variation (e.g., Article, NewsArticle, BlogPosting)
- Special characters and HTML entities
- Edge cases with unusual data
- Different data combinations
Example structure:
```
examples/app-router-showcase/app/
├── article/ # Basic article example
├── article-advanced/ # All features
├── news-article/ # NewsArticle type
├── blog-posting/ # BlogPosting type
└── article-special-chars/ # Special characters test
```
You should also add a valid JSON test in `tests/e2e/jsonValidation.e2e.spec.ts`
### Security and Escaping Tests
**DO NOT add escape/security tests to individual component E2E tests!**
Security testing for escaping dangerous sequences (like `</script>`, HTML comments, etc.) is handled centrally in `tests/e2e/security.e2e.spec.ts`. This test file comprehensively covers:
- Script tag injection prevention
- HTML comment escaping
- Edge cases with mixed dangerous patterns
- Safe rendering in Next.js-like environments
Individual component E2E tests should focus on:
- Component-specific functionality
- Correct data structure output
- Schema type variations
- Required and optional properties
The escaping functionality is a core library feature handled by the `stringify` utility, not something each component needs to test individually.
## 9. Final Verification
Before completing, run all quality checks:
```bash
# 1. Run unit tests
pnpm test:unit
# 2. Type checking
pnpm typecheck
# 3. Linting
pnpm lint
# 4. Build the package
pnpm build
```
Developer will run e2e manually as they can take a long time.
## Common Patterns and Best Practices
### Shared Utilities
The library now provides shared utilities to avoid code duplication:
1. **Common Types** (`~/types/common.types.ts`):
- `ImageObject`, `Person`, `Organization`, `Author`
- Base interfaces like `Thing`
2. **Processing Functions** (`~/utils/processors.ts`):
- `processAuthor(author: Author): Person | Organization`
- `processImage(image: string | ImageObject): string | ImageObject`
### Flexible Input Processing
Use the shared processing functions from `~/utils/processors`:
```typescript
import { processAuthor, processImage } from "~/utils/processors";
// These functions handle string-to-object conversions automatically
// and add the appropriate @type without developers needing to specify it
```
**Important**: Always create or use existing process functions for properties that can accept multiple formats. This maintains the pattern of not requiring developers to specify `@type` and ensures consistent behavior across all components.
### Conditional Property Inclusion
Use object spread with conditional checks:
```typescript
const data = {
"@context": "https://schema.org",
"@type": type,
headline,
...(url && { url }), // Only include if truthy
...(isAccessibleForFree !== undefined && { isAccessibleForFree }), // Include false values
};
```
### Default Values
Apply sensible defaults where appropriate:
```typescript
// If dateModified is not provided but datePublished is, use datePublished
...(!dateModified && datePublished && { dateModified: datePublished }),
```
### Array Handling
Support both single items and arrays:
```typescript
...(author && {
author: Array.isArray(author)
? author.map(processAuthor)
: processAuthor(author),
}),
```
## Troubleshooting
### Common Issues
1. **ESLint errors about unused React import**
- Remove `import React from 'react'` - it's not needed with modern JSX transform
2. **Test failures with dateModified**
- Remember that dateModified defaults to datePublished when not provided
3. **Boolean properties not appearing**
- Use `!== undefined` check instead of truthy check for booleans
4. **Type errors with union types**
- Ensure proper type guards in processing functions
## Checklist for New Components
- [ ] Research Google's structured data documentation
- [ ] Create comprehensive type definitions (reuse common types from `common.types.ts`)
- [ ] Implement component using shared utilities from `~/utils/processors`
- [ ] Update exports in src/index.ts
- [ ] Write unit tests covering all scenarios
- [ ] Add documentation to README.md
- [ ] Create example pages for each variation
- [ ] Write E2E tests (Double check guidelines!)
- [ ] Run all quality checks (full sweep can be done via `pnpm test:sweep`)
- [ ] Ensure backward compatibility if updating existing component
- [ ] Check if any new processing functions should be added to shared utilities
================================================
FILE: AGENTS.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Next SEO is a plugin that makes managing SEO easier in Next.js projects. It's built with TypeScript and provides components for structured data (JSON-LD) and SEO management.
## Critical Rules
You must check these after coming up with a plan
[ ] Your plan adheres to the guide found in @ADDING_NEW_COMPONENTS.md
[ ] Your plan adheres to the guidelines found below
## Development Commands
### Installation
```bash
pnpm install
```
### Build & Development
```bash
pnpm dev # Watch mode with tsup
pnpm build # Build library code
```
### Code Quality
```bash
pnpm lint # Run ESLint
pnpm lint:fix # Fix ESLint issues
pnpm format # Format with Prettier
pnpm typecheck # Type checking with TypeScript
```
### Testing
```bash
pnpm test # Run typecheck + lint only
pnpm test:unit # Run unit tests with Vitest
pnpm test:unit:watch # Watch mode for unit tests
pnpm coverage # Generate coverage report
# Requires pnpm build to run first
pnpm test:e2e # Run E2E tests with Playwright
pnpm test:e2e:ui # Run E2E tests with UI
```
### Example App
```bash
pnpm example:dev # Run example app at localhost:3001
pnpm example:build # Build example app
pnpm example:start # Start example app
```
### Utilities
```bash
pnpm clean # Clean build artifacts
```
## Project Architecture
### Core Structure
- **`/src`** - Library source code
- **`/core`** - Core components like `JsonLdScript`
- **`/types`** - TypeScript type definitions
- **`/utils`** - Utility functions like `stringify`
- **`/examples/app-router-showcase`** - Example Next.js app for testing
- **`/tests`** - Test files
- **`/unit`** - Unit tests (Vitest)
- **`/e2e`** - E2E tests (Playwright)
### Build Configuration
- **tsup** - For building the library (see `tsup.config.ts`)
- Outputs both CommonJS and ESM formats
- Path alias: `~` maps to `./src`
### Testing Setup
- **Vitest** - Unit testing with React Testing Library
- **Playwright** - E2E testing running against example app on port 3001
- Tests use `~` alias for imports
## Development Notes
1. All library code is in `/src` directory
2. The project uses pnpm workspaces with the example app
3. When developing, the example app auto-starts on port 3001 for E2E tests
4. Lint and format are automatically run on staged files via Husky
5. The library exports both CommonJS and ESM formats with TypeScript definitions
6. When adding a new component ALWAYS refer to the guide found in ADDING_NEW_COMPONENTS.md
## Key Patterns
### @type Optional Pattern
Next SEO provides excellent developer experience by **never requiring developers to manually specify `@type` properties**. This is achieved through intelligent type definitions and process functions.
#### How It Works:
1. **Type Definitions**: Component props use `Omit<Type, "@type">` to make `@type` optional
2. **Process Functions**: Automatically add the correct `@type` based on the input
3. **Flexible Inputs**: Accept strings, objects with/without `@type`, and arrays
#### Example:
```typescript
// Developers can write this:
<ArticleJsonLd
author="John Doe" // Simple string
publisher={{ name: "ACME Corp", logo: "logo.jpg" }} // No @type needed
/>
// Process functions transform it to valid Schema.org:
{
author: { "@type": "Person", name: "John Doe" },
publisher: { "@type": "Organization", name: "ACME Corp", logo: {...} }
}
```
#### Benefits:
- **Better DX**: No need to remember Schema.org type names
- **Less Boilerplate**: Cleaner, more readable code
- **Type Safety**: Full TypeScript support maintained
- **Flexibility**: Still accepts objects with `@type` if provided
#### Implementation Rules:
1. Always use process functions for properties accepting flexible types
2. Never require `@type` in component props
3. Use intelligent detection (e.g., `logo` property → Organization)
4. Provide sensible defaults in process functions
This pattern is fundamental to the library's design and must be maintained in all components.
================================================
FILE: CHANGELOG.md
================================================
# next-seo
## 7.2.0
### Minor Changes
- 28c684e: Add `review` and `aggregateRating` props to OrganizationJsonLd component, matching the existing support in LocalBusinessJsonLd. Both are direct Schema.org Organization properties processed using shared utilities.
## 7.1.0
### Minor Changes
- d412e2b: Add HowToJsonLd component for structured data support
- New `HowToJsonLd` component following Schema.org HowTo specification
- Support for HowToStep, HowToSection, HowToDirection, and HowToTip types
- HowToSupply and HowToTool for materials and equipment
- Duration properties (prepTime, performTime, totalTime) in ISO 8601 format
- estimatedCost as string or MonetaryAmount object
- yield as string or QuantitativeValue
- Video support via VideoObject
## 7.0.1
### Patch Changes
- 1db3648: Add JSDoc comment to internal type guard function
================================================
FILE: CLAUDE.md
================================================
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Next SEO is a plugin that makes managing SEO easier in Next.js projects. It's built with TypeScript and provides components for structured data (JSON-LD) and SEO management.
## Critical Rules
You must check these after coming up with a plan
[ ] Your plan adheres to the guide found in @ADDING_NEW_COMPONENTS.md
[ ] Your plan adheres to the guidelines found below
## Development Commands
### Installation
```bash
pnpm install
```
### Build & Development
```bash
pnpm dev # Watch mode with tsup
pnpm build # Build library code
```
### Code Quality
```bash
pnpm lint # Run ESLint
pnpm lint:fix # Fix ESLint issues
pnpm format # Format with Prettier
pnpm typecheck # Type checking with TypeScript
```
### Testing
```bash
pnpm test # Run typecheck + lint only
pnpm test:unit # Run unit tests with Vitest
pnpm test:unit:watch # Watch mode for unit tests
pnpm coverage # Generate coverage report
# Requires pnpm build to run first
pnpm test:e2e # Run E2E tests with Playwright
pnpm test:e2e:ui # Run E2E tests with UI
```
### Example App
```bash
pnpm example:dev # Run example app at localhost:3001
pnpm example:build # Build example app
pnpm example:start # Start example app
```
### Utilities
```bash
pnpm clean # Clean build artifacts
```
## Project Architecture
### Core Structure
- **`/src`** - Library source code
- **`/core`** - Core components like `JsonLdScript`
- **`/types`** - TypeScript type definitions
- **`/utils`** - Utility functions like `stringify`
- **`/examples/app-router-showcase`** - Example Next.js app for testing
- **`/tests`** - Test files
- **`/unit`** - Unit tests (Vitest)
- **`/e2e`** - E2E tests (Playwright)
### Build Configuration
- **tsup** - For building the library (see `tsup.config.ts`)
- Outputs both CommonJS and ESM formats
- Path alias: `~` maps to `./src`
### Testing Setup
- **Vitest** - Unit testing with React Testing Library
- **Playwright** - E2E testing running against example app on port 3001
- Tests use `~` alias for imports
## Development Notes
1. All library code is in `/src` directory
2. The project uses pnpm workspaces with the example app
3. When developing, the example app auto-starts on port 3001 for E2E tests
4. Lint and format are automatically run on staged files via Husky
5. The library exports both CommonJS and ESM formats with TypeScript definitions
6. When adding a new component ALWAYS refer to the guide found in ADDING_NEW_COMPONENTS.md
## Key Patterns
### @type Optional Pattern
Next SEO provides excellent developer experience by **never requiring developers to manually specify `@type` properties**. This is achieved through intelligent type definitions and process functions.
#### How It Works:
1. **Type Definitions**: Component props use `Omit<Type, "@type">` to make `@type` optional
2. **Process Functions**: Automatically add the correct `@type` based on the input
3. **Flexible Inputs**: Accept strings, objects with/without `@type`, and arrays
#### Example:
```typescript
// Developers can write this:
<ArticleJsonLd
author="John Doe" // Simple string
publisher={{ name: "ACME Corp", logo: "logo.jpg" }} // No @type needed
/>
// Process functions transform it to valid Schema.org:
{
author: { "@type": "Person", name: "John Doe" },
publisher: { "@type": "Organization", name: "ACME Corp", logo: {...} }
}
```
#### Benefits:
- **Better DX**: No need to remember Schema.org type names
- **Less Boilerplate**: Cleaner, more readable code
- **Type Safety**: Full TypeScript support maintained
- **Flexibility**: Still accepts objects with `@type` if provided
#### Implementation Rules:
1. Always use process functions for properties accepting flexible types
2. Never require `@type` in component props
3. Use intelligent detection (e.g., `logo` property → Organization)
4. Provide sensible defaults in process functions
This pattern is fundamental to the library's design and must be maintained in all components.
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to Next SEO
Thank you for your interest in contributing to Next SEO! We are open to all and any contributions. This guide will help you get started.
It is critical that you look over the guidance for new components [here](ADDING_NEW_COMPONENTS.md)
## 🚀 Quick Start
1. Fork the repository
2. Clone your fork: `git clone git@github.com:your-username/next-seo.git`
3. Install dependencies: `pnpm install`
4. Create a new branch: `git checkout -b feature/your-feature-name`
5. Make your changes
6. Add a changeset: `pnpm changeset`
7. Submit a pull request
## 📦 Development Setup
### Prerequisites
- Node.js 20+ (LTS recommended)
- pnpm 9+ (`npm install -g pnpm`)
### Installation
```bash
# Clone the repository
git clone git@github.com:garmeeh/next-seo.git
cd next-seo
# Install dependencies
pnpm install
# Start development (watch mode)
pnpm dev
```
### Available Commands
```bash
pnpm dev # Watch mode development
pnpm build # Build the library
pnpm test # Run type checking and linting
pnpm test:unit # Run unit tests
pnpm test:e2e # Run E2E tests (requires build first)
pnpm test:sweep # Run full test suite (CI equivalent)
pnpm lint # Check linting
pnpm format # Format code with Prettier
```
## 📝 Adding a Changeset
**Important:** All PRs with code changes require a changeset. This helps us track changes and automatically manage releases.
### What is a changeset?
A changeset is a piece of information about changes made in a branch or commit. It includes:
- What packages changed
- What kind of change it was (major/minor/patch)
- A description of the change for the changelog
### How to add a changeset:
1. After making your changes, run: `pnpm changeset`
2. Select the packages affected (usually just `next-seo`)
3. Choose the type of change:
- **patch**: Bug fixes, documentation, internal changes (0.0.X) **Rarely use this, generally only for security, since this is an SEO package I don't want patches to slip through for people without validating**
- **minor**: New features, non-breaking enhancements (0.X.0) **Most common**
- **major**: Breaking changes (X.0.0)
4. Write a brief description of your changes
5. Commit the generated changeset file
### Example:
```bash
$ pnpm changeset
🦋 Which packages would you like to include? › next-seo
🦋 Which packages should have a major bump? › (none)
🦋 Which packages should have a minor bump? › next-seo
🦋 The following packages will be patch bumped:
🦋 next-seo@minor
🦋 Please enter a summary for this change:
📝 Added support for RecipeJsonLd component with full Schema.org compliance
```
We recommend never using patch unless critical security bug
This creates a markdown file in `.changeset/` that will be used to:
- Update the package version
- Generate changelog entries
- Credit you as a contributor
### When is a changeset NOT required?
- Documentation-only changes (README, etc.)
- Changes to GitHub workflows
- Changes to development tooling that don't affect the published package
## 🏗️ Project Guidelines
### For AI-Assisted Development
This project leverages AI coding tools. If you're using tools like Claude or GitHub Copilot:
- Refer to [CLAUDE.md](CLAUDE.md) for project-specific AI guidance
- Refer to [ADDING_NEW_COMPONENTS.md](ADDING_NEW_COMPONENTS.md) for component development
### For Large Features
If you're planning a large feature or refactor:
1. Open an issue first to discuss with maintainers
2. Provide comprehensive context in your issue/PR
3. Break down large changes into smaller, reviewable PRs if possible
## 🧪 Testing Requirements
Before submitting a PR, ensure all tests pass:
```bash
# Quick checks
pnpm test # Type checking and linting
pnpm test:unit # Unit tests
# Full validation (what CI runs)
pnpm test:sweep # Complete test suite
```
## 🔄 Pull Request Process
1. **Fork & Clone**: Fork the repo and clone locally
2. **Branch**: Create a feature branch from `main`
3. **Develop**: Make your changes following our guidelines
4. **Changeset**: Add a changeset describing your changes
5. **Test**: Ensure all tests pass
6. **Push**: Push to your fork
7. **PR**: Open a pull request with a clear description
### PR Guidelines
- Use clear, descriptive titles
- Reference any related issues
- Include examples if adding new features
- Ensure CI passes before requesting review
## ❓ Questions?
- Open a [Discussion](https://github.com/garmeeh/next-seo/discussions) for general questions
- Check existing issues and PRs
- Refer to the [README](./README.md) for usage documentation
## 📄 License
By contributing, you agree that your contributions will be licensed under the same MIT License that covers this project.
---
Thank you for contributing to Next SEO! Your efforts help make SEO easier for the Next.js community.
================================================
FILE: CUSTOM_COMPONENTS.md
================================================
# Creating Custom JSON-LD Components with Next SEO
This guide shows you how to create your own structured data components using next-seo's core utilities, maintaining the same excellent developer experience as the built-in components.
## Table of Contents
1. [Quick Start](#quick-start)
2. [Core Concepts](#core-concepts)
3. [Using Built-in Processors](#using-built-in-processors)
4. [Creating Custom Processors](#creating-custom-processors)
5. [Advanced Patterns](#advanced-patterns)
6. [Best Practices](#best-practices)
7. [Real-World Examples](#real-world-examples)
## Quick Start
Create a custom JSON-LD component in just a few lines:
```tsx
import { JsonLdScript, processors } from "next-seo";
export function PodcastEpisodeJsonLd({ name, author, duration, url }) {
const data = {
"@context": "https://schema.org",
"@type": "PodcastEpisode",
name,
...(url && { url }),
...(duration && { duration }),
...(author && { author: processors.processAuthor(author) }),
};
return <JsonLdScript data={data} scriptKey="podcast-episode" />;
}
// Usage - no @type needed!
<PodcastEpisodeJsonLd
name="Episode 1: Getting Started"
author="Jane Doe" // Simple string works!
duration="PT30M"
url="https://example.com/episode-1"
/>;
```
## Core Concepts
### The JsonLdScript Component
The `JsonLdScript` component is the foundation for rendering structured data:
```tsx
import { JsonLdScript } from "next-seo";
<JsonLdScript
data={yourStructuredData}
id={optionalId} // Optional: HTML id attribute
scriptKey={requiredKey} // Required: React key for the script element
/>;
```
### The @type Optional Pattern
Next SEO's key principle: **developers should never need to specify @type manually**. This is achieved through intelligent processors that automatically add the correct Schema.org types.
```tsx
// Your users write this:
author="John Doe"
// Your processor converts it to:
{ "@type": "Person", name: "John Doe" }
```
### Processors
Processors are functions that transform flexible inputs into properly typed Schema.org objects:
```tsx
import { processors } from "next-seo";
// Use built-in processors for common types
const author = processors.processAuthor("John Doe");
const image = processors.processImage({ url: "image.jpg", width: 800 });
const address = processors.processAddress("123 Main St");
```
## Using Built-in Processors
Next SEO provides 60+ processors for common Schema.org types:
### People & Organizations
```tsx
import { processors } from "next-seo";
// Flexible author input
processors.processAuthor("Jane Doe"); // → Person
processors.processAuthor({ name: "ACME Corp", logo: "..." }); // → Organization
// Other people/org processors
processors.processPublisher("Tech Publishing");
processors.processOrganizer({ name: "Event Co", url: "..." });
processors.processPerformer("Band Name");
```
### Media & Content
```tsx
// Images - string URL or ImageObject
processors.processImage("https://example.com/image.jpg");
processors.processImage({ url: "...", width: 800, height: 600 });
// Videos
processors.processVideo({
name: "Tutorial",
uploadDate: "2024-01-01",
thumbnailUrl: "...",
});
// Other media processors
processors.processLogo("logo.jpg");
processors.processScreenshot({ url: "...", caption: "App screenshot" });
```
### Locations & Places
```tsx
// Simple string becomes PostalAddress
processors.processAddress("123 Main St, City, Country");
// Object with more details
processors.processAddress({
streetAddress: "123 Main St",
addressLocality: "San Francisco",
addressRegion: "CA",
postalCode: "94105",
addressCountry: "US",
});
// Places with geo coordinates
processors.processPlace({
name: "Office",
geo: { latitude: 37.7749, longitude: -122.4194 },
});
```
### Commerce & Offers
```tsx
// Product offers
processors.processProductOffer({
price: 29.99,
priceCurrency: "USD",
availability: "https://schema.org/InStock",
});
// Return policies
processors.processMerchantReturnPolicy({
returnPolicyCategory: "https://schema.org/MerchantReturnFiniteReturnWindow",
merchantReturnDays: 30,
});
```
## Creating Custom Processors
### Basic Custom Processor
Create processors for your specific needs:
```tsx
import { processors } from "next-seo";
// Custom processor for a podcast host
function processHost(host: string | { name: string; bio?: string }) {
if (typeof host === "string") {
return {
"@type": "Person",
name: host,
};
}
// Use the generic helper for objects
return processors.processSchemaType(host, "Person");
}
// Use in your component
export function PodcastJsonLd({ hosts, ...props }) {
const data = {
"@context": "https://schema.org",
"@type": "PodcastSeries",
...(hosts && {
host: Array.isArray(hosts) ? hosts.map(processHost) : processHost(hosts),
}),
};
return <JsonLdScript data={data} scriptKey="podcast" />;
}
```
### Advanced Custom Processor with Type Detection
Intelligently determine the type based on input properties:
```tsx
function processCreativeWork(work: string | Record<string, any>) {
if (typeof work === "string") {
return {
"@type": "CreativeWork",
name: work,
};
}
// Already has @type? Return as-is
if (work["@type"]) {
return work;
}
// Detect type based on properties
let type = "CreativeWork";
if ("isbn" in work) type = "Book";
else if ("director" in work) type = "Movie";
else if ("artist" in work) type = "MusicRecording";
return {
"@type": type,
...work,
};
}
```
## Advanced Patterns
### Nested Processing
Process nested structures recursively:
```tsx
function processEventWithVenue(event: {
name: string;
venue?: string | { name: string; address?: string };
organizer?: string | { name: string };
}) {
return {
"@type": "Event",
name: event.name,
...(event.venue && {
location:
typeof event.venue === "string"
? processors.processPlace(event.venue)
: processors.processPlace({
...event.venue,
...(event.venue.address && {
address: processors.processAddress(event.venue.address),
}),
}),
}),
...(event.organizer && {
organizer: processors.processOrganizer(event.organizer),
}),
};
}
```
### Conditional Properties
Include properties only when they have values:
```tsx
export function CustomProductJsonLd({
name,
description,
price,
image,
brand,
reviews,
aggregateRating,
...props
}) {
const data = {
"@context": "https://schema.org",
"@type": "Product",
name,
...(description && { description }),
...(price && {
offers: {
"@type": "Offer",
price,
priceCurrency: "USD",
},
}),
...(image && {
image: Array.isArray(image)
? image.map(processors.processImage)
: processors.processImage(image),
}),
...(brand && { brand: processors.processBrand(brand) }),
...(reviews && {
review: Array.isArray(reviews)
? reviews.map(processors.processReview)
: processors.processReview(reviews),
}),
...(aggregateRating && {
aggregateRating: processors.processAggregateRating(aggregateRating),
}),
};
return <JsonLdScript data={data} scriptKey={props.scriptKey || "product"} />;
}
```
### Multiple Schema Types
Support different schema types with a type prop:
```tsx
type ScholarlyArticleType =
| "ScholarlyArticle"
| "MedicalScholarlyArticle"
| "TechArticle";
export function ScholarlyArticleJsonLd({
type = "ScholarlyArticle",
headline,
author,
datePublished,
journal,
doi,
...props
}: {
type?: ScholarlyArticleType;
headline: string;
author: string | Array<string | { name: string }>;
datePublished: string;
journal?: string;
doi?: string;
}) {
const data = {
"@context": "https://schema.org",
"@type": type,
headline,
datePublished,
...(author && {
author: Array.isArray(author)
? author.map(processors.processAuthor)
: processors.processAuthor(author),
}),
...(journal && {
isPartOf: {
"@type": "PublicationIssue",
name: journal,
},
}),
...(doi && { identifier: processors.processIdentifier(doi) }),
};
return (
<JsonLdScript
data={data}
scriptKey={props.scriptKey || `article-${type}`}
/>
);
}
```
## Best Practices
### 1. Always Use Processors for Flexible Types
```tsx
// ✅ Good - uses processor
author: processors.processAuthor(author)
// ❌ Bad - requires user to specify @type
author: { "@type": "Person", ...author }
```
### 2. Handle Arrays and Single Values
```tsx
// Support both single and array inputs
...(tags && {
keywords: Array.isArray(tags) ? tags.join(', ') : tags
})
```
### 3. Apply Sensible Defaults
```tsx
// Default dateModified to datePublished if not provided
const data = {
datePublished,
dateModified: dateModified || datePublished,
};
```
### 4. Use TypeScript for Better DX
```tsx
interface ServiceJsonLdProps {
name: string;
provider?: string | Organization;
areaServed?: string | string[];
serviceType?: string;
scriptId?: string;
scriptKey?: string;
}
```
### 5. Document Your Component
```tsx
/**
* ServiceJsonLd - Structured data for service offerings
*
* @example
* <ServiceJsonLd
* name="Web Development"
* provider="Tech Agency"
* areaServed={["US", "CA", "UK"]}
* serviceType="Professional Service"
* />
*/
export function ServiceJsonLd({ ... }) { ... }
```
## Real-World Examples
### 1. Podcast Series with Episodes
```tsx
import { JsonLdScript, processors } from "next-seo";
interface PodcastSeriesProps {
name: string;
description?: string;
host?: string | Array<string | { name: string; url?: string }>;
episodes?: Array<{
name: string;
url?: string;
duration?: string;
datePublished?: string;
}>;
image?: string | { url: string; width?: number; height?: number };
scriptKey?: string;
}
export function PodcastSeriesJsonLd({
name,
description,
host,
episodes,
image,
scriptKey = "podcast-series",
}: PodcastSeriesProps) {
const data = {
"@context": "https://schema.org",
"@type": "PodcastSeries",
name,
...(description && { description }),
...(host && {
host: Array.isArray(host)
? host.map((h) =>
typeof h === "string"
? { "@type": "Person", name: h }
: processors.processAuthor(h),
)
: typeof host === "string"
? { "@type": "Person", name: host }
: processors.processAuthor(host),
}),
...(image && { image: processors.processImage(image) }),
...(episodes && {
episode: episodes.map((ep, index) => ({
"@type": "PodcastEpisode",
name: ep.name,
position: index + 1,
...(ep.url && { url: ep.url }),
...(ep.duration && { duration: ep.duration }),
...(ep.datePublished && { datePublished: ep.datePublished }),
})),
}),
};
return <JsonLdScript data={data} scriptKey={scriptKey} />;
}
```
### 2. Real Estate Listing
```tsx
import { JsonLdScript, processors, type ImageObject } from "next-seo";
interface RealEstateListingProps {
name: string;
description?: string;
price: number;
priceCurrency?: string;
address: string | Record<string, any>;
images?: Array<string | ImageObject>;
numberOfRooms?: number;
floorSize?: { value: number; unitCode: string };
yearBuilt?: number;
scriptKey?: string;
}
export function RealEstateListingJsonLd({
name,
description,
price,
priceCurrency = "USD",
address,
images,
numberOfRooms,
floorSize,
yearBuilt,
scriptKey = "real-estate",
}: RealEstateListingProps) {
const data = {
"@context": "https://schema.org",
"@type": "RealEstateListing",
name,
...(description && { description }),
offers: {
"@type": "Offer",
price,
priceCurrency,
},
address: processors.processAddress(address),
...(images && {
image: images.map(processors.processImage),
}),
...(numberOfRooms && { numberOfRooms }),
...(floorSize && {
floorSize: processors.processQuantitativeValue(floorSize),
}),
...(yearBuilt && { yearBuilt }),
};
return <JsonLdScript data={data} scriptKey={scriptKey} />;
}
```
### 3. Service with Pricing Tiers
```tsx
import { JsonLdScript, processors } from "next-seo";
interface ServiceWithPricingProps {
name: string;
provider: string | { name: string; url?: string };
description?: string;
pricingTiers?: Array<{
name: string;
price: number | { min: number; max: number };
features?: string[];
}>;
areaServed?: string | string[];
scriptKey?: string;
}
export function ServiceWithPricingJsonLd({
name,
provider,
description,
pricingTiers,
areaServed,
scriptKey = "service",
}: ServiceWithPricingProps) {
const data = {
"@context": "https://schema.org",
"@type": "Service",
name,
provider: processors.processOrganization(provider),
...(description && { description }),
...(pricingTiers && {
hasOfferCatalog: {
"@type": "OfferCatalog",
name: `${name} Pricing`,
itemListElement: pricingTiers.map((tier) => ({
"@type": "Offer",
name: tier.name,
...(typeof tier.price === "number"
? { price: tier.price }
: {
priceSpecification: {
"@type": "PriceSpecification",
minPrice: tier.price.min,
maxPrice: tier.price.max,
priceCurrency: "USD",
},
}),
...(tier.features && {
description: tier.features.join(", "),
}),
})),
},
}),
...(areaServed && {
areaServed: Array.isArray(areaServed) ? areaServed : [areaServed],
}),
};
return <JsonLdScript data={data} scriptKey={scriptKey} />;
}
```
### 4. Educational Course with Modules
```tsx
import { JsonLdScript, processors } from "next-seo";
interface CourseWithModulesProps {
name: string;
description: string;
provider: string | { name: string; url?: string };
instructor?: string | Array<string | { name: string }>;
modules?: Array<{
name: string;
description?: string;
duration?: string;
}>;
price?: number;
startDate?: string;
endDate?: string;
scriptKey?: string;
}
export function CourseWithModulesJsonLd({
name,
description,
provider,
instructor,
modules,
price,
startDate,
endDate,
scriptKey = "course",
}: CourseWithModulesProps) {
const data = {
"@context": "https://schema.org",
"@type": "Course",
name,
description,
provider: processors.processProvider(provider),
...(instructor && {
instructor: Array.isArray(instructor)
? instructor.map(processors.processAuthor)
: processors.processAuthor(instructor),
}),
...(modules && {
hasCourseInstance: modules.map((module, index) => ({
"@type": "CourseInstance",
name: module.name,
courseMode: "online",
position: index + 1,
...(module.description && { description: module.description }),
...(module.duration && { duration: module.duration }),
})),
}),
...(price !== undefined && {
offers: {
"@type": "Offer",
price,
priceCurrency: "USD",
...(startDate && { validFrom: startDate }),
...(endDate && { validThrough: endDate }),
},
}),
};
return <JsonLdScript data={data} scriptKey={scriptKey} />;
}
```
## Testing Your Components
### Unit Testing
```tsx
import { render } from "@testing-library/react";
import { ServiceJsonLd } from "./ServiceJsonLd";
describe("ServiceJsonLd", () => {
it("renders service with basic props", () => {
const { container } = render(
<ServiceJsonLd name="Consulting Service" provider="Tech Solutions Inc" />,
);
const script = container.querySelector(
'script[type="application/ld+json"]',
);
const data = JSON.parse(script.textContent);
expect(data["@type"]).toBe("Service");
expect(data.name).toBe("Consulting Service");
expect(data.provider["@type"]).toBe("Organization");
});
});
```
### Validation
Use Google's Rich Results Test to validate your structured data:
1. Deploy your page with the custom component
2. Visit [Google Rich Results Test](https://search.google.com/test/rich-results)
3. Enter your URL and check for errors
## Migration Guide
If you're migrating from inline JSON-LD to next-seo custom components:
### Before (Inline JSON-LD)
```tsx
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "Service",
name: "My Service",
provider: {
"@type": "Organization",
name: "My Company",
},
}),
}}
/>
```
### After (Custom Component)
```tsx
import { JsonLdScript, processors } from "next-seo";
export function ServiceJsonLd({ name, provider }) {
const data = {
"@context": "https://schema.org",
"@type": "Service",
name,
provider: processors.processOrganization(provider),
};
return <JsonLdScript data={data} scriptKey="service" />;
}
// Usage - cleaner and type-safe!
<ServiceJsonLd
name="My Service"
provider="My Company" // No @type needed!
/>;
```
## Processor API Reference
For a complete list of available processors, see the [processors export file](./src/utils/processors.export.ts). Key processors include:
- `processSchemaType(value, type)` - Generic processor for any schema type
- `processAuthor(author)` - Person or Organization
- `processImage(image)` - String URL or ImageObject
- `processAddress(address)` - String or PostalAddress
- `processPlace(place)` - String or Place with address
- `processOffer(offer)` - Offer with price and availability
- `processReview(review)` - Review with rating and author
- `processAggregateRating(rating)` - Aggregate rating with count
- And 50+ more specialized processors...
## Getting Help
- Check existing components in [src/components](./src/components) for patterns
- Review [ADDING_NEW_COMPONENTS.md](./ADDING_NEW_COMPONENTS.md) for internal component development
- Open an issue for processor requests or questions
- Contribute new processors via PR
## Summary
Creating custom JSON-LD components with next-seo is simple:
1. Import `JsonLdScript` and `processors`
2. Define your component props (TypeScript recommended)
3. Use processors for flexible input handling
4. Apply the @type optional pattern
5. Return JsonLdScript with your data
This approach gives you:
- ✅ Type safety with TypeScript
- ✅ Flexible input handling
- ✅ No @type boilerplate for users
- ✅ Consistent with next-seo patterns
- ✅ Easy to test and maintain
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2018 Gary Meehan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: LICENSE.md
================================================
MIT License
Copyright (c) 2018 Gary Meehan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: LIST.md
================================================
# Google Search Supported Structured Data Components
This list tracks which Google Search supported structured data types are implemented in next-seo.
## Implementation Status
- [x] Article
- [~] Book actions (Upcoming Deprecation)
- [x] Breadcrumb
- [x] Carousel
- [~] Course info (Upcoming Deprecation)
- [x] Course list
- [x] Dataset
- [x] Discussion forum
- [x] Education Q&A (implemented as QuizJsonLd)
- [x] Employer aggregate rating
- [~] Estimated salary (Upcoming Deprecation)
- [x] Event
- [x] Fact check
- [x] FAQ
- [x] Image metadata
- [x] Job posting
- [~] Learning video (Upcoming Deprecation)
- [x] Local business
- [ ] Math solver
- [x] Movie carousel
- [x] Organization
- [ ] Practice problem
- [x] Product
- [x] Merchant Listing
- [x] Variants
- [x] Loyalty Program
- [x] Merchant Return Policy
- [x] Profile page
- [x] Q&A
- [x] Recipe
- [x] Review snippet
- [x] Software app
- [ ] Speakable
- [~] Special announcement (Upcoming Deprecation)
- [x] Subscription and paywalled content
- [x] Vacation rental
- [~] Vehicle listing (Upcoming Deprecation)
- [x] Video
## Notes
- Education Q&A is implemented as `QuizJsonLd` in next-seo
- Some components marked with icons in the Google documentation may have special requirements or be in beta
================================================
FILE: README.md
================================================
**Outrank**
Get traffic and outrank competitors with Backlinks & SEO-optimized content while you sleep! I've been keeping a close eye on this new tool and it seems to be gaining a lot of traction and delivering great results. [Try it now!](https://outrank.so/?via=next-seo)
[](https://outrank.so/?via=next-seo)
**Have you seen the new Next.js newsletter?**
[<img alt="NextjsWeekly banner" src="./next-js-weekly.png">](https://dub.sh/nextjsweekly)
# Next SEO




Next SEO is a plugin that makes managing your SEO easier in Next.js projects. It provides components for structured data (JSON-LD) that helps search engines understand your content better.
## 📋 Table of Contents
_Looking for v6 documentation? [View Here](https://github.com/garmeeh/next-seo/tree/master)_
_Still using <NextSeo /> component in Pages? View docs here [/src/pages/README.md]_
## 🚀 Quick Start
### Installation
```bash
npm install next-seo
# or
yarn add next-seo
# or
pnpm add next-seo
# or
bun add next-seo
```
### Basic Usage
```tsx
import { ArticleJsonLd } from "next-seo";
export default function BlogPost() {
return (
<>
<ArticleJsonLd
headline="Getting Started with Next SEO"
datePublished="2024-01-01T08:00:00+00:00"
author="John Doe"
image="https://example.com/article-image.jpg"
description="Learn how to improve your Next.js SEO"
/>
<article>
<h1>Getting Started with Next SEO</h1>
{/* Your content */}
</article>
</>
);
}
```
> **Note**: For standard meta tags (`<meta>`, `<title>`), use Next.js's built-in [`generateMetadata`](https://nextjs.org/docs/app/api-reference/functions/generate-metadata) function.
> **Pages Router Support**: If you're using Next.js Pages Router, import components from `next-seo/pages`. See the [Pages Router documentation](./src/pages/README.md) for details.
## Support This Project
**Feel like supporting this free plugin?**
It takes a lot of time to maintain an open source project so any small contribution is greatly appreciated.
Coffee fuels coding ☕️
<a href="https://www.buymeacoffee.com/garmeeh" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
## Components
### ArticleJsonLd
The `ArticleJsonLd` component helps you add structured data for articles, blog posts, and news articles to improve their appearance in search results.
#### Basic Usage
```tsx
import { ArticleJsonLd } from "next-seo";
export default function ArticlePage() {
return (
<>
<ArticleJsonLd
headline="My Amazing Article"
datePublished="2024-01-01T08:00:00+08:00"
author="John Doe"
image="https://example.com/article-image.jpg"
description="This article explains amazing things about Next.js SEO"
/>
<article>
<h1>My Amazing Article</h1>
{/* Article content */}
</article>
</>
);
}
```
#### Advanced Example with Multiple Authors
```tsx
<ArticleJsonLd
type="NewsArticle"
headline="Breaking: Next SEO v7 Released"
url="https://example.com/news/next-seo-v7"
datePublished="2024-01-01T08:00:00+08:00"
dateModified="2024-01-02T10:00:00+08:00"
author={[
{
"@type": "Person",
name: "Jane Smith",
url: "https://example.com/authors/jane",
},
"John Doe", // Can mix objects and strings
]}
image={[
"https://example.com/images/16x9.jpg",
"https://example.com/images/4x3.jpg",
"https://example.com/images/1x1.jpg",
]}
publisher={{
"@type": "Organization",
name: "Example News",
logo: "https://example.com/logo.png",
}}
isAccessibleForFree={true}
/>
```
#### Blog Posting Example
```tsx
<ArticleJsonLd
type="BlogPosting"
headline="10 Tips for Better SEO"
url="https://example.com/blog/seo-tips"
datePublished="2024-01-01T08:00:00+08:00"
author={{
"@type": "Organization",
name: "SEO Experts Inc.",
url: "https://example.com",
}}
image={{
"@type": "ImageObject",
url: "https://example.com/blog-hero.jpg",
width: 1200,
height: 630,
caption: "SEO Tips Illustration",
}}
description="Learn the top 10 tips to improve your website's SEO"
mainEntityOfPage={{
"@type": "WebPage",
"@id": "https://example.com/blog/seo-tips",
}}
/>
```
#### Props
| Property | Type | Description |
| --------------------- | ------------------------------------------------------- | -------------------------------------------------------- |
| `type` | `"Article" \| "NewsArticle" \| "BlogPosting" \| "Blog"` | The type of article. Defaults to "Article" |
| `headline` | `string` | **Required.** The headline of the article |
| `url` | `string` | The canonical URL of the article |
| `author` | `string \| Person \| Organization \| Author[]` | The author(s) of the article |
| `datePublished` | `string` | ISO 8601 date when the article was published |
| `dateModified` | `string` | ISO 8601 date when the article was last modified |
| `image` | `string \| ImageObject \| (string \| ImageObject)[]` | Article images. Google recommends multiple aspect ratios |
| `publisher` | `Organization` | The publisher of the article |
| `description` | `string` | A short description of the article |
| `isAccessibleForFree` | `boolean` | Whether the article is accessible for free |
| `mainEntityOfPage` | `string \| WebPage` | Indicates the article is the primary content of the page |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key prop for React |
#### Best Practices
1. **Always include images**: Google strongly recommends including high-resolution images with multiple aspect ratios (16x9, 4x3, 1x1)
2. **Use ISO 8601 dates**: Include timezone information for accuracy
3. **Multiple authors**: List all authors when applicable
4. **Publisher logo**: Include a logo for NewsArticle type
5. **Update dateModified**: Keep this current when updating content
[↑ Back to Components](#-components-by-category)
### ClaimReviewJsonLd
The `ClaimReviewJsonLd` component helps you add structured data for fact-checking articles that review claims made by others. This enables a summarized version of your fact check to display in Google Search results.
#### Basic Usage
```tsx
import { ClaimReviewJsonLd } from "next-seo";
export default function FactCheckPage() {
return (
<>
<ClaimReviewJsonLd
claimReviewed="The world is flat"
reviewRating={{
ratingValue: 1,
bestRating: 5,
worstRating: 1,
alternateName: "False",
}}
url="https://example.com/fact-check/flat-earth"
author="Fact Check Team"
/>
<article>
<h1>Fact Check: The World is Flat</h1>
{/* Your fact check content */}
</article>
</>
);
}
```
#### Props
| Property | Type | Description |
| --------------- | ---------------------------------- | ------------------------------------------------------------------------------------- |
| `claimReviewed` | `string` | **Required.** A short summary of the claim being evaluated (keep under 75 characters) |
| `reviewRating` | `object` | **Required.** The assessment of the claim with rating value and textual rating |
| `url` | `string` | **Required.** Link to the page hosting the full fact check article |
| `author` | `string \| Organization \| Person` | The publisher of the fact check article |
| `itemReviewed` | `Claim` | Detailed information about the claim being reviewed |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key for script identification |
#### Review Rating Properties
| Property | Type | Description |
| --------------- | -------- | ------------------------------------------------------------------------------------------- |
| `alternateName` | `string` | **Required.** The truthfulness rating as human-readable text (e.g., "False", "Mostly true") |
| `ratingValue` | `number` | **Required.** Numeric rating (closer to bestRating = more true) |
| `bestRating` | `number` | Best value in the rating scale (must be greater than worstRating) |
| `worstRating` | `number` | Worst value in the rating scale (minimum value of 1) |
| `name` | `string` | Alternative to alternateName (use alternateName instead) |
#### Advanced Example with Claim Details
```tsx
<ClaimReviewJsonLd
claimReviewed="Climate change is not real"
reviewRating={{
ratingValue: 1,
bestRating: 5,
worstRating: 1,
alternateName: "Pants on Fire",
}}
url="https://example.com/fact-check/climate-denial"
author={{
name: "Climate Facts Organization",
url: "https://example.com",
logo: "https://example.com/logo.jpg",
}}
itemReviewed={{
author: {
name: "Climate Denial Institute",
sameAs: "https://climatedenial.example.com",
},
datePublished: "2024-06-20",
appearance: {
url: "https://example.com/original-claim",
headline: "The Great Climate Hoax",
datePublished: "2024-06-22",
author: "John Doe",
publisher: {
name: "Denial News",
logo: "https://example.com/denial-logo.jpg",
},
},
}}
/>
```
#### Best Practices
1. **Clear ratings**: Use descriptive alternateName values that clearly indicate the verdict
2. **Claim summary**: Keep claimReviewed concise (under 75 characters) to prevent wrapping
3. **Full context**: Include itemReviewed when possible to provide claim origin details
4. **Consistent scale**: Use a consistent rating scale across all your fact checks
5. **Author credibility**: Clearly identify your fact-checking organization
[↑ Back to Components](#-components-by-category)
### CreativeWorkJsonLd
The `CreativeWorkJsonLd` component helps you add structured data for various types of creative content, with special support for marking paywalled or subscription-based content. This enables Google to differentiate paywalled content from cloaking practices.
#### Basic Usage
```tsx
import { CreativeWorkJsonLd } from "next-seo";
export default function ArticlePage() {
return (
<>
<CreativeWorkJsonLd
type="Article"
headline="Premium Article"
datePublished="2024-01-01T08:00:00+08:00"
author="John Doe"
description="This premium article requires a subscription"
isAccessibleForFree={false}
hasPart={{
isAccessibleForFree: false,
cssSelector: ".paywall",
}}
/>
<article>
<h1>Premium Article</h1>
<div className="non-paywall">Free preview content here...</div>
<div className="paywall">
Premium content that requires subscription...
</div>
</article>
</>
);
}
```
#### Props
| Property | Type | Description |
| --------------------- | --------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| `type` | `"CreativeWork" \| "Article" \| "NewsArticle" \| "Blog" \| "BlogPosting" \| "Comment" \| ...` | The type of creative work. Defaults to "CreativeWork" |
| `headline` | `string` | The headline of the content (used for Article types) |
| `name` | `string` | The name of the content (alternative to headline) |
| `url` | `string` | URL of the content |
| `author` | `string \| Person \| Organization \| Array` | Author(s) of the content |
| `datePublished` | `string` | ISO 8601 publication date |
| `dateModified` | `string` | ISO 8601 modification date |
| `image` | `string \| ImageObject \| Array` | Image(s) associated with the content |
| `publisher` | `string \| Organization \| Person` | Publisher of the content |
| `description` | `string` | Description of the content |
| `isAccessibleForFree` | `boolean` | Whether the content is free or requires payment/subscription |
| `hasPart` | `WebPageElement \| WebPageElement[]` | Marks specific sections as paywalled |
| `mainEntityOfPage` | `string \| WebPage` | The main page for this content |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key for script identification |
#### WebPageElement Properties (for hasPart)
| Property | Type | Description |
| --------------------- | --------- | --------------------------------------------------- |
| `isAccessibleForFree` | `boolean` | **Required.** Whether this section is free (false) |
| `cssSelector` | `string` | **Required.** CSS class selector (e.g., ".paywall") |
#### Marking Paywalled Content
```tsx
<CreativeWorkJsonLd
type="NewsArticle"
headline="Breaking News: Premium Coverage"
datePublished="2024-01-01T08:00:00+00:00"
isAccessibleForFree={false}
hasPart={{
isAccessibleForFree: false,
cssSelector: ".premium-content",
}}
/>
```
#### Multiple Paywalled Sections
```tsx
<CreativeWorkJsonLd
type="Article"
headline="In-Depth Analysis"
datePublished="2024-01-01T08:00:00+00:00"
isAccessibleForFree={false}
hasPart={[
{
isAccessibleForFree: false,
cssSelector: ".section1",
},
{
isAccessibleForFree: false,
cssSelector: ".section2",
},
]}
/>
```
#### Different CreativeWork Types
```tsx
// Blog with subscription content
<CreativeWorkJsonLd
type="Blog"
name="Premium Tech Blog"
description="Technology insights for subscribers"
isAccessibleForFree={false}
/>
// Comment
<CreativeWorkJsonLd
type="Comment"
text="Great article!"
author="Jane Smith"
datePublished="2024-01-01T10:00:00+00:00"
/>
// Course with provider
<CreativeWorkJsonLd
type="Course"
name="Advanced Programming"
provider="Tech University"
description="Learn advanced programming concepts"
isAccessibleForFree={false}
/>
// Review with rating
<CreativeWorkJsonLd
type="Review"
name="Product Review"
itemReviewed="Amazing Gadget"
reviewRating={{
ratingValue: 4.5,
bestRating: 5,
}}
author="Tech Reviewer"
/>
```
#### Best Practices
1. **Use specific types**: Choose the most specific CreativeWork type (Article, NewsArticle, etc.) when applicable
2. **Mark paywalled sections**: Use `hasPart` with `cssSelector` to identify paywalled content sections
3. **Class selectors only**: Only use class selectors (e.g., ".paywall") for `cssSelector`, not IDs or other selectors
4. **Consistent selectors**: Ensure your HTML classes match the `cssSelector` values exactly
5. **Complete metadata**: Include as much metadata as possible (author, dates, images) for better search results
[↑ Back to Components](#-components-by-category)
### RecipeJsonLd
The `RecipeJsonLd` component helps you add structured data for recipes to improve their appearance in search results with rich snippets that can include ratings, cooking times, and images.
#### Basic Usage
```tsx
import { RecipeJsonLd } from "next-seo";
export default function RecipePage() {
return (
<>
<RecipeJsonLd
name="Simple Chocolate Chip Cookies"
image="https://example.com/cookies.jpg"
description="Classic chocolate chip cookies that are crispy on the outside and chewy on the inside"
author="Baker Jane"
datePublished="2024-01-01T08:00:00+00:00"
prepTime="PT20M"
cookTime="PT12M"
totalTime="PT32M"
recipeYield="24 cookies"
recipeCategory="dessert"
recipeCuisine="American"
recipeIngredient={[
"2 1/4 cups all-purpose flour",
"1 cup butter, softened",
"3/4 cup granulated sugar",
"3/4 cup packed brown sugar",
"2 large eggs",
"2 teaspoons vanilla extract",
"1 teaspoon baking soda",
"1 teaspoon salt",
"2 cups chocolate chips",
]}
recipeInstructions={[
"Preheat oven to 375°F (190°C)",
"Mix flour, baking soda, and salt in a bowl",
"In another bowl, cream butter and sugars until fluffy",
"Beat in eggs and vanilla",
"Gradually blend in flour mixture",
"Stir in chocolate chips",
"Drop by rounded tablespoons onto ungreased cookie sheets",
"Bake for 9 to 11 minutes or until golden brown",
]}
/>
<article>
<h1>Simple Chocolate Chip Cookies</h1>
{/* Recipe content */}
</article>
</>
);
}
```
#### Advanced Example with Structured Instructions and Nutrition
```tsx
<RecipeJsonLd
name="Gourmet Lasagna"
image={[
"https://example.com/lasagna-16x9.jpg",
"https://example.com/lasagna-4x3.jpg",
"https://example.com/lasagna-1x1.jpg",
]}
description="A rich and hearty lasagna with layers of meat sauce, cheese, and pasta"
author={{
"@type": "Organization",
name: "The Italian Kitchen",
url: "https://example.com",
}}
datePublished="2024-01-15T10:00:00+00:00"
url="https://example.com/recipes/gourmet-lasagna"
prepTime="PT45M"
cookTime="PT1H"
totalTime="PT1H45M"
recipeYield={8}
recipeCategory="main course"
recipeCuisine="Italian"
keywords="lasagna, italian, pasta, cheese"
recipeIngredient={[
"1 pound ground beef",
"1 onion, chopped",
"2 cloves garlic, minced",
"1 can (28 oz) crushed tomatoes",
"2 cans (6 oz each) tomato paste",
"16 oz ricotta cheese",
"1 egg",
"12 lasagna noodles",
"16 oz mozzarella cheese, shredded",
]}
recipeInstructions={[
{
"@type": "HowToStep",
name: "Prepare the meat sauce",
text: "Brown ground beef with onion and garlic. Add tomatoes and tomato paste. Simmer for 30 minutes.",
},
{
"@type": "HowToStep",
name: "Prepare cheese mixture",
text: "Mix ricotta cheese with egg and half of the mozzarella",
},
{
"@type": "HowToStep",
name: "Assemble lasagna",
text: "Layer meat sauce, noodles, and cheese mixture in a 9x13 pan. Repeat layers.",
},
{
"@type": "HowToStep",
name: "Bake",
text: "Cover with foil and bake at 375°F for 45 minutes. Remove foil, add remaining mozzarella, and bake 15 more minutes.",
},
]}
nutrition={{
"@type": "NutritionInformation",
calories: "450 calories",
proteinContent: "28g",
carbohydrateContent: "35g",
fatContent: "22g",
saturatedFatContent: "10g",
sodiumContent: "680mg",
fiberContent: "3g",
servingSize: "1 piece (1/8 of recipe)",
}}
aggregateRating={{
"@type": "AggregateRating",
ratingValue: 4.7,
ratingCount: 234,
reviewCount: 189,
}}
video={{
"@type": "VideoObject",
name: "How to Make Gourmet Lasagna",
description: "Watch our chef prepare this delicious lasagna step by step",
thumbnailUrl: "https://example.com/lasagna-video-thumb.jpg",
contentUrl: "https://example.com/videos/lasagna-tutorial.mp4",
embedUrl: "https://example.com/embed/lasagna-tutorial",
uploadDate: "2024-01-10T08:00:00+00:00",
duration: "PT8M30S",
}}
/>
```
#### Props
| Property | Type | Description |
| -------------------- | -------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |
| `name` | `string` | **Required.** The name of the dish |
| `image` | `string \| ImageObject \| (string \| ImageObject)[]` | **Required.** Images of the completed dish. Google recommends multiple high-resolution images |
| `description` | `string` | A short summary describing the dish |
| `author` | `string \| Person \| Organization` | The creator of the recipe |
| `datePublished` | `string` | ISO 8601 date when the recipe was published |
| `url` | `string` | The canonical URL of the recipe page |
| `prepTime` | `string` | ISO 8601 duration for preparation time (e.g., "PT30M" for 30 minutes) |
| `cookTime` | `string` | ISO 8601 duration for cooking time |
| `totalTime` | `string` | ISO 8601 duration for total time (prep + cook) |
| `recipeYield` | `string \| number` | The quantity produced (e.g., "4 servings", "1 loaf", or just 6) |
| `recipeCategory` | `string` | The type of meal or course (e.g., "dessert", "main course") |
| `recipeCuisine` | `string` | The cuisine of the recipe (e.g., "French", "Mexican") |
| `recipeIngredient` | `string[]` | List of ingredients with quantities |
| `recipeInstructions` | `string \| HowToStep \| HowToSection \| (string \| HowToStep \| HowToSection)[]` | Step-by-step instructions |
| `nutrition` | `NutritionInformation` | Nutritional information per serving |
| `aggregateRating` | `AggregateRating` | The aggregate rating from users |
| `video` | `VideoObject` | A video showing how to make the recipe |
| `keywords` | `string` | Keywords about the recipe, separated by commas |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key prop for React |
#### Duration Format (ISO 8601)
Use these formats for time durations:
- `PT15M` - 15 minutes
- `PT1H` - 1 hour
- `PT1H30M` - 1 hour 30 minutes
- `PT2H15M` - 2 hours 15 minutes
#### Best Practices
1. **High-quality images**: Include multiple high-resolution images (16x9, 4x3, 1x1 aspect ratios)
2. **Detailed instructions**: Use HowToStep objects for better structured data
3. **Complete nutrition info**: Include nutrition data when possible for better search visibility
4. **Accurate times**: Always provide prepTime and cookTime together
5. **Ratings**: Include aggregateRating when you have user reviews
6. **Video content**: Adding a video significantly improves search appearance
[↑ Back to Components](#-components-by-category)
### HowToJsonLd
The `HowToJsonLd` component helps you add structured data for how-to guides and tutorials. This can help your content appear as rich results with step-by-step instructions in search results.
#### Basic Usage
```tsx
import { HowToJsonLd } from "next-seo";
export default function HowToPage() {
return (
<>
<HowToJsonLd
name="How to Change a Flat Tire"
description="Step-by-step instructions for safely changing a flat tire"
image="https://example.com/tire-change.jpg"
totalTime="PT30M"
estimatedCost="$20"
supply={["Spare tire", "Wheel wedges"]}
tool={["Lug wrench", "Jack"]}
step={[
"Turn on hazard lights and apply wheel wedges",
"Remove the hubcap and loosen lug nuts",
"Position jack and raise the vehicle",
"Remove flat tire and mount spare",
"Lower vehicle and tighten lug nuts",
]}
/>
<article>
<h1>How to Change a Flat Tire</h1>
{/* Guide content */}
</article>
</>
);
}
```
#### Advanced Example with Sections and Detailed Steps
```tsx
<HowToJsonLd
name="How to Change a Flat Tire"
description="Complete guide to safely changing a flat tire on the roadside"
image={{
url: "https://example.com/tire-change-guide.jpg",
width: 1200,
height: 800,
}}
estimatedCost={{
currency: "USD",
value: 20,
}}
prepTime="PT5M"
performTime="PT25M"
totalTime="PT30M"
yield="1 changed tire"
tool={[
{
name: "Spare tire",
},
{
name: "Lug wrench",
image: "https://example.com/lug-wrench.jpg",
},
{
name: "Jack",
},
{
name: "Wheel wedges",
image: "https://example.com/wheel-wedges.jpg",
},
]}
supply={[
{
name: "Flares",
image: "https://example.com/flares.jpg",
},
]}
step={[
{
"@type": "HowToSection",
name: "Preparation",
position: 1,
itemListElement: [
{
"@type": "HowToStep",
position: 1,
itemListElement: [
{
"@type": "HowToDirection",
position: 1,
text: "Turn on your hazard lights and set the flares.",
},
{
"@type": "HowToTip",
position: 2,
text: "You're going to need space and want to be visible.",
},
],
},
{
"@type": "HowToStep",
position: 2,
itemListElement: [
{
"@type": "HowToDirection",
position: 1,
text: "Position wheel wedges in front of front tires if rear tire is flat, or behind rear tires if front tire is flat.",
},
{
"@type": "HowToTip",
position: 2,
text: "You don't want the car to move while you're working on it.",
},
],
},
],
},
{
"@type": "HowToSection",
name: "Raise the Car",
position: 2,
itemListElement: [
{
"@type": "HowToStep",
position: 1,
text: "Position the jack underneath the car, next to the flat tire.",
image: "https://example.com/position-jack.jpg",
},
{
"@type": "HowToStep",
position: 2,
text: "Raise the jack until the flat tire is just barely off of the ground.",
},
{
"@type": "HowToStep",
position: 3,
text: "Remove the hubcap and loosen the lug nuts.",
},
],
},
{
"@type": "HowToSection",
name: "Finishing Up",
position: 3,
itemListElement: [
{
"@type": "HowToStep",
position: 1,
text: "Lower the jack and tighten the lug nuts with the wrench.",
},
{
"@type": "HowToStep",
position: 2,
text: "Replace the hubcap.",
},
{
"@type": "HowToStep",
position: 3,
text: "Put the equipment and the flat tire away.",
},
],
},
]}
video={{
name: "How to Change a Tire Video Tutorial",
description: "Watch our mechanic demonstrate the proper technique",
thumbnailUrl: "https://example.com/video-thumb.jpg",
contentUrl: "https://example.com/tire-change-video.mp4",
uploadDate: "2024-01-15T08:00:00+00:00",
duration: "PT8M",
}}
/>
```
#### Props
| Property | Type | Description |
| --------------- | ---------------------------------------------------- | ------------------------------------------------------------------------- |
| `name` | `string` | **Required.** The title of the how-to guide |
| `description` | `string` | A brief description of the guide |
| `image` | `string \| ImageObject` | An image of the completed task or project |
| `estimatedCost` | `string \| MonetaryAmount` | The estimated cost of supplies (e.g., "$20" or MonetaryAmount object) |
| `prepTime` | `string` | ISO 8601 duration for preparation time |
| `performTime` | `string` | ISO 8601 duration for the time to perform the instructions |
| `totalTime` | `string` | ISO 8601 duration for total time (prep + perform) |
| `yield` | `string \| QuantitativeValue` | The result of performing the instructions (e.g., "1 birdhouse") |
| `supply` | `string \| HowToSupply \| (string \| HowToSupply)[]` | Supplies consumed when performing the task |
| `tool` | `string \| HowToTool \| (string \| HowToTool)[]` | Tools used but not consumed |
| `step` | `string \| HowToStep \| HowToSection \| (Step)[]` | The steps to complete the task. Can be simple strings, steps, or sections |
| `video` | `VideoObject` | A video showing how to complete the task |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key prop for React |
#### Step Types
**HowToStep** - A single step in the guide:
```tsx
{
"@type": "HowToStep",
name: "Step Name", // Optional step title
text: "Step instructions", // The instruction text
url: "https://...", // Optional URL for more details
image: "https://...", // Optional step image
}
```
**HowToSection** - A group of related steps:
```tsx
{
"@type": "HowToSection",
name: "Section Name",
position: 1,
itemListElement: [
{ "@type": "HowToStep", text: "First step" },
{ "@type": "HowToStep", text: "Second step" },
]
}
```
**HowToDirection** and **HowToTip** - For detailed step content:
```tsx
{
"@type": "HowToStep",
itemListElement: [
{
"@type": "HowToDirection",
text: "Do this specific action",
beforeMedia: "https://example.com/before.jpg",
afterMedia: "https://example.com/after.jpg",
},
{
"@type": "HowToTip",
text: "Here's a helpful tip",
}
]
}
```
#### Duration Format (ISO 8601)
Use these formats for time durations:
- `PT15M` - 15 minutes
- `PT1H` - 1 hour
- `PT1H30M` - 1 hour 30 minutes
- `PT2H15M` - 2 hours 15 minutes
#### Best Practices
1. **Clear steps**: Write concise, actionable step instructions
2. **Include images**: Add images for complex steps to improve clarity
3. **Separate sections**: Use HowToSection to group related steps logically
4. **Accurate timing**: Provide realistic time estimates for each phase
5. **List all materials**: Include all supplies and tools needed upfront
6. **Add video**: Video content significantly improves search appearance
[↑ Back to Components](#-components-by-category)
### OrganizationJsonLd
The `OrganizationJsonLd` component helps you add structured data about your organization to improve how it appears in search results and knowledge panels.
#### Basic Usage
```tsx
import { OrganizationJsonLd } from "next-seo";
export default function AboutPage() {
return (
<>
<OrganizationJsonLd
name="Example Corporation"
url="https://www.example.com"
logo="https://www.example.com/logo.png"
description="The example corporation is well-known for producing high-quality widgets"
sameAs={[
"https://twitter.com/example",
"https://facebook.com/example",
"https://linkedin.com/company/example",
]}
/>
<div>
<h1>About Example Corporation</h1>
{/* About page content */}
</div>
</>
);
}
```
#### Advanced Example with Address and Contact
```tsx
<OrganizationJsonLd
type="Organization"
name="Example Corporation"
url="https://www.example.com"
logo={{
"@type": "ImageObject",
url: "https://www.example.com/logo.png",
width: 600,
height: 400,
}}
description="Leading provider of innovative widget solutions"
sameAs={[
"https://example.net/profile/example1234",
"https://example.org/example1234",
]}
address={{
"@type": "PostalAddress",
streetAddress: "999 W Example St Suite 99",
addressLocality: "New York",
addressRegion: "NY",
postalCode: "10019",
addressCountry: "US",
}}
contactPoint={{
"@type": "ContactPoint",
contactType: "Customer Service",
telephone: "+1-999-999-9999",
email: "support@example.com",
}}
telephone="+1-999-999-9999"
email="contact@example.com"
foundingDate="2010-01-01"
vatID="FR12345678901"
iso6523Code="0199:724500PMK2A2M1SQQ228"
numberOfEmployees={{
minValue: 100,
maxValue: 999,
}}
/>
```
#### OnlineStore Example with Return Policy
```tsx
<OrganizationJsonLd
type="OnlineStore"
name="Example Online Store"
url="https://www.example.com"
logo="https://www.example.com/assets/logo.png"
contactPoint={{
"@type": "ContactPoint",
contactType: "Customer Service",
email: "support@example.com",
telephone: "+47-99-999-9900",
}}
vatID="FR12345678901"
iso6523Code="0199:724500PMK2A2M1SQQ228"
hasMerchantReturnPolicy={{
"@type": "MerchantReturnPolicy",
applicableCountry: ["FR", "CH"],
returnPolicyCountry: "FR",
returnPolicyCategory: "https://schema.org/MerchantReturnFiniteReturnWindow",
merchantReturnDays: 60,
returnMethod: "https://schema.org/ReturnByMail",
returnFees: "https://schema.org/FreeReturn",
refundType: "https://schema.org/FullRefund",
}}
/>
```
#### Props
| Property | Type | Description |
| ------------------------- | -------------------------------------------------------- | ------------------------------------------------------- |
| `type` | `"Organization" \| "OnlineStore"` | The type of organization. Defaults to "Organization" |
| `name` | `string` | The name of your organization |
| `url` | `string` | The URL of your organization's website |
| `logo` | `string \| ImageObject` | Your organization's logo (112x112px minimum) |
| `description` | `string` | A detailed description of your organization |
| `sameAs` | `string \| string[]` | URLs of your organization's profiles on other sites |
| `address` | `string \| PostalAddress \| (string \| PostalAddress)[]` | Physical or mailing address(es) |
| `contactPoint` | `ContactPoint \| ContactPoint[]` | Contact information for your organization |
| `telephone` | `string` | Primary phone number (include country code) |
| `email` | `string` | Primary email address |
| `alternateName` | `string` | Alternative name your organization goes by |
| `foundingDate` | `string` | ISO 8601 date when the organization was founded |
| `legalName` | `string` | Registered legal name if different from name |
| `taxID` | `string` | Tax ID associated with your organization |
| `vatID` | `string` | VAT code (important trust signal) |
| `duns` | `string` | Dun & Bradstreet DUNS number |
| `leiCode` | `string` | Legal Entity Identifier (ISO 17442) |
| `naics` | `string` | North American Industry Classification System code |
| `globalLocationNumber` | `string` | GS1 Global Location Number |
| `iso6523Code` | `string` | ISO 6523 identifier (e.g., "0199:724500PMK2A2M1SQQ228") |
| `numberOfEmployees` | `number \| QuantitativeValue` | Number of employees or range |
| `review` | `Review \| Review[]` | A review or array of reviews of the organization |
| `aggregateRating` | `AggregateRating` | The overall rating based on a collection of reviews |
| `hasMerchantReturnPolicy` | `MerchantReturnPolicy \| MerchantReturnPolicy[]` | Return policy details (OnlineStore only) |
| `hasMemberProgram` | `MemberProgram \| MemberProgram[]` | Loyalty/membership program details (OnlineStore only) |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key prop for React |
#### Organization with Reviews and Ratings
```tsx
<OrganizationJsonLd
name="Acme Software Inc."
url="https://www.acmesoftware.com"
logo="https://www.acmesoftware.com/logo.png"
review={[
{
author: "Sarah Johnson",
reviewBody: "Excellent company to work with!",
reviewRating: {
ratingValue: 5,
bestRating: 5,
},
datePublished: "2025-06-15",
},
{
author: "Michael Chen",
reviewBody: "Great software solutions with excellent customer service.",
reviewRating: {
ratingValue: 4,
bestRating: 5,
},
datePublished: "2025-08-22",
},
]}
aggregateRating={{
ratingValue: 4.6,
ratingCount: 312,
reviewCount: 245,
bestRating: 5,
worstRating: 1,
}}
/>
```
#### OnlineStore with Loyalty Program Example
```tsx
<OrganizationJsonLd
type="OnlineStore"
name="Example Store"
url="https://www.example.com"
hasMemberProgram={{
name: "Rewards Plus",
description:
"Earn points and unlock exclusive benefits with our loyalty program",
url: "https://www.example.com/rewards",
hasTiers: [
{
name: "Bronze",
hasTierBenefit: "TierBenefitLoyaltyPoints",
membershipPointsEarned: 1,
},
{
name: "Silver",
hasTierBenefit: ["TierBenefitLoyaltyPoints"],
hasTierRequirement: {
value: 500,
currency: "USD",
},
membershipPointsEarned: 2,
},
{
name: "Gold",
hasTierBenefit: ["TierBenefitLoyaltyPoints", "TierBenefitLoyaltyPrice"],
hasTierRequirement: {
name: "Example Gold Credit Card",
},
membershipPointsEarned: 5,
url: "https://www.example.com/rewards/gold",
},
],
}}
/>
```
#### Multiple Loyalty Programs Example
```tsx
<OrganizationJsonLd
type="OnlineStore"
name="Premium Store"
hasMemberProgram={[
{
name: "Basic Rewards",
description: "Standard loyalty program for all customers",
hasTiers: {
name: "Member",
hasTierBenefit: "TierBenefitLoyaltyPoints",
membershipPointsEarned: 1,
},
},
{
name: "VIP Elite",
description: "Exclusive program for premium members",
hasTiers: [
{
name: "Silver VIP",
hasTierBenefit: [
"TierBenefitLoyaltyPoints",
"TierBenefitLoyaltyPrice",
],
hasTierRequirement: {
value: 2500,
currency: "USD",
},
membershipPointsEarned: {
value: 10,
unitText: "points per dollar",
},
},
{
name: "Gold VIP",
hasTierBenefit: [
"TierBenefitLoyaltyPoints",
"TierBenefitLoyaltyPrice",
],
hasTierRequirement: {
price: 9.99,
priceCurrency: "USD",
billingDuration: 12,
billingIncrement: 1,
unitCode: "MON",
},
membershipPointsEarned: 20,
},
],
},
]}
/>
```
#### MemberProgram Properties
| Property | Type | Description |
| ------------- | ------------------------------------------ | --------------------------------------------- |
| `name` | `string` | **Required**. Name of the loyalty program |
| `description` | `string` | **Required**. Description of program benefits |
| `url` | `string` | URL where customers can sign up |
| `hasTiers` | `MemberProgramTier \| MemberProgramTier[]` | **Required**. Tier(s) of the loyalty program |
#### MemberProgramTier Properties
| Property | Type | Description |
| ------------------------ | ----------------------------- | ------------------------------------ |
| `name` | `string` | **Required**. Name of the tier |
| `hasTierBenefit` | `string \| string[]` | **Required**. Benefits for this tier |
| `hasTierRequirement` | `various` (see below) | Requirements to join this tier |
| `membershipPointsEarned` | `number \| QuantitativeValue` | Points earned per unit spent |
| `url` | `string` | URL for tier-specific signup |
| `@id` | `string` | Unique identifier for the tier |
#### Tier Benefits
Benefits can be specified using short names or full URLs:
- `"TierBenefitLoyaltyPoints"` or `"https://schema.org/TierBenefitLoyaltyPoints"` - Earn loyalty points
- `"TierBenefitLoyaltyPrice"` or `"https://schema.org/TierBenefitLoyaltyPrice"` - Special member pricing
#### Tier Requirements
The `hasTierRequirement` property accepts different types based on the requirement:
**Credit Card Requirement:**
```tsx
hasTierRequirement: {
name: "Store Premium Credit Card";
}
```
**Minimum Spending Requirement (MonetaryAmount):**
```tsx
hasTierRequirement: {
value: 1000,
currency: "USD"
}
```
**Subscription Fee (UnitPriceSpecification):**
```tsx
hasTierRequirement: {
price: 9.99,
priceCurrency: "EUR",
billingDuration: 12, // Total duration
billingIncrement: 1, // Billing frequency
unitCode: "MON" // Unit (MON = monthly)
}
```
**Text Description:**
```tsx
hasTierRequirement: "By invitation only - must maintain $10,000+ annual spending";
```
#### Membership Points Earned
Points can be specified as a simple number or as a detailed QuantitativeValue:
**Simple:**
```tsx
membershipPointsEarned: 5;
```
**Detailed:**
```tsx
membershipPointsEarned: {
value: 10,
minValue: 10,
maxValue: 20,
unitText: "points per dollar (double on special events)"
}
```
#### Best Practices
1. **Place on homepage or about page**: Add this markup to your homepage or a dedicated "about us" page
2. **Use specific subtypes**: Use "OnlineStore" for e-commerce sites rather than generic "Organization"
3. **Include identifiers**: Add VAT ID, ISO codes, and other identifiers for better trust signals
4. **Complete address information**: Provide full address details including country code
5. **Multiple locations**: Use array format for addresses if you have multiple locations
6. **High-quality logo**: Use a logo that looks good on white background, minimum 112x112px
[↑ Back to Components](#-components-by-category)
### LocalBusinessJsonLd
The `LocalBusinessJsonLd` component helps you add structured data for local businesses to improve their appearance in Google Search and Maps results, including knowledge panels and local business carousels.
#### Basic Usage
```tsx
import { LocalBusinessJsonLd } from "next-seo";
<LocalBusinessJsonLd
type="Restaurant"
name="Dave's Steak House"
address={{
"@type": "PostalAddress",
streetAddress: "148 W 51st St",
addressLocality: "New York",
addressRegion: "NY",
postalCode: "10019",
addressCountry: "US",
}}
telephone="+12125551234"
url="https://www.example.com"
priceRange="$$$"
/>;
```
#### Restaurant Example with Full Details
```tsx
<LocalBusinessJsonLd
type="Restaurant"
name="Dave's Steak House"
address={{
"@type": "PostalAddress",
streetAddress: "148 W 51st St",
addressLocality: "New York",
addressRegion: "NY",
postalCode: "10019",
addressCountry: "US",
}}
geo={{
"@type": "GeoCoordinates",
latitude: 40.761293,
longitude: -73.982294,
}}
url="https://www.example.com/restaurant-locations/manhattan"
telephone="+12122459600"
image={[
"https://example.com/photos/1x1/photo.jpg",
"https://example.com/photos/4x3/photo.jpg",
"https://example.com/photos/16x9/photo.jpg",
]}
servesCuisine="American"
priceRange="$$$"
openingHoursSpecification={[
{
"@type": "OpeningHoursSpecification",
dayOfWeek: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
opens: "11:30",
closes: "22:00",
},
{
"@type": "OpeningHoursSpecification",
dayOfWeek: "Saturday",
opens: "16:00",
closes: "23:00",
},
{
"@type": "OpeningHoursSpecification",
dayOfWeek: "Sunday",
opens: "16:00",
closes: "22:00",
},
]}
menu="https://www.example.com/menu"
aggregateRating={{
"@type": "AggregateRating",
ratingValue: 4.5,
ratingCount: 250,
}}
/>
```
#### Store with Departments
```tsx
<LocalBusinessJsonLd
type="Store"
name="Dave's Department Store"
address={{
"@type": "PostalAddress",
streetAddress: "1600 Saratoga Ave",
addressLocality: "San Jose",
addressRegion: "CA",
postalCode: "95129",
addressCountry: "US",
}}
telephone="+14088717984"
department={[
{
type: "Pharmacy",
name: "Dave's Pharmacy",
address: "1600 Saratoga Ave, San Jose, CA 95129",
telephone: "+14088719385",
openingHoursSpecification: {
"@type": "OpeningHoursSpecification",
dayOfWeek: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
opens: "09:00",
closes: "19:00",
},
},
]}
/>
```
#### Props
| Property | Type | Description |
| --------------------------- | ---------------------------------------------------------- | -------------------------------------------------------------------------- |
| `type` | `string \| string[]` | Business type (e.g., "Restaurant", "Store", or ["Restaurant", "BarOrPub"]) |
| `name` | `string` | **Required.** The name of the business |
| `address` | `string \| PostalAddress \| (string \| PostalAddress)[]` | **Required.** Physical location(s) of the business |
| `url` | `string` | The fully-qualified URL of the business location page |
| `telephone` | `string` | Primary contact phone number (include country code) |
| `image` | `string \| ImageObject \| (string \| ImageObject)[]` | Images of the business (multiple aspect ratios recommended) |
| `priceRange` | `string` | Relative price range (e.g., "$", "$$", "$$$", or "$10-15") |
| `geo` | `GeoCoordinates` | Geographic coordinates (min 5 decimal places precision) |
| `openingHoursSpecification` | `OpeningHoursSpecification \| OpeningHoursSpecification[]` | Business hours including special/seasonal hours |
| `review` | `Review \| Review[]` | Customer reviews (for review sites only) |
| `aggregateRating` | `AggregateRating` | Average rating based on multiple reviews |
| `department` | `LocalBusinessBase \| LocalBusinessBase[]` | Departments within the business |
| `menu` | `string` | URL of the menu (for food establishments) |
| `servesCuisine` | `string \| string[]` | Type of cuisine served (for restaurants) |
| `sameAs` | `string \| string[]` | URLs of business profiles on other sites |
| `branchOf` | `Organization` | Parent organization if this is a branch |
| `currenciesAccepted` | `string` | Currencies accepted (e.g., "USD") |
| `paymentAccepted` | `string` | Payment methods accepted |
| `areaServed` | `string \| string[]` | Geographic areas served |
| `email` | `string` | Business email address |
| `faxNumber` | `string` | Business fax number |
| `slogan` | `string` | Business slogan or tagline |
| `description` | `string` | Detailed description of the business |
| `publicAccess` | `boolean` | Whether the business location is accessible to the public |
| `smokingAllowed` | `boolean` | Whether smoking is allowed at the location |
| `isAccessibleForFree` | `boolean` | Whether access is free |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key prop for React |
#### Opening Hours Examples
**Standard Business Hours:**
```tsx
openingHoursSpecification={[
{
"@type": "OpeningHoursSpecification",
dayOfWeek: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
opens: "09:00",
closes: "17:00",
},
{
"@type": "OpeningHoursSpecification",
dayOfWeek: ["Saturday", "Sunday"],
opens: "10:00",
closes: "16:00",
},
]}
```
**24/7 Operation:**
```tsx
openingHoursSpecification={{
"@type": "OpeningHoursSpecification",
dayOfWeek: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
opens: "00:00",
closes: "23:59",
}}
```
**Closed on Specific Days:**
```tsx
openingHoursSpecification={{
"@type": "OpeningHoursSpecification",
dayOfWeek: "Sunday",
opens: "00:00",
closes: "00:00",
}}
```
**Seasonal Hours:**
```tsx
openingHoursSpecification={{
"@type": "OpeningHoursSpecification",
dayOfWeek: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"],
opens: "10:00",
closes: "18:00",
validFrom: "2024-06-01",
validThrough: "2024-09-30",
}}
```
#### Best Practices
1. **Use specific business types**: Use the most specific LocalBusiness subtype (e.g., "Restaurant" instead of "LocalBusiness")
2. **Multiple types**: For businesses that fit multiple categories, use an array (e.g., `["Restaurant", "BarOrPub"]`)
3. **Complete address**: Provide as many address fields as possible for better local SEO
4. **High-quality images**: Include multiple images with different aspect ratios (16:9, 4:3, 1:1)
5. **Accurate coordinates**: Use at least 5 decimal places for latitude and longitude
6. **Opening hours**: Be precise with opening hours and include seasonal variations
7. **Department naming**: Include the main store name with department name (e.g., "Store Name - Pharmacy")
8. **Price range**: Keep under 100 characters; use standard symbols ($, $$, $$$) or ranges
[↑ Back to Components](#-components-by-category)
### MerchantReturnPolicyJsonLd
The `MerchantReturnPolicyJsonLd` component helps you add structured data for merchant return policies, enabling Google Search to display return policy information alongside your products and in knowledge panels. This component supports both detailed policy specifications and simple links to policy pages.
#### Basic Usage - Option A (Detailed Properties)
Use this pattern when you want to provide detailed return policy information:
```tsx
import { MerchantReturnPolicyJsonLd } from "next-seo";
<MerchantReturnPolicyJsonLd
applicableCountry={["US", "CA"]}
returnPolicyCountry="US"
returnPolicyCategory="https://schema.org/MerchantReturnFiniteReturnWindow"
merchantReturnDays={30}
returnMethod="https://schema.org/ReturnByMail"
returnFees="https://schema.org/FreeReturn"
refundType="https://schema.org/FullRefund"
returnLabelSource="https://schema.org/ReturnLabelDownloadAndPrint"
/>;
```
#### Basic Usage - Option B (Link Only)
Use this pattern when you prefer to link to your return policy page:
```tsx
import { MerchantReturnPolicyJsonLd } from "next-seo";
<MerchantReturnPolicyJsonLd merchantReturnLink="https://www.example.com/returns" />;
```
#### Advanced Usage with All Features
```tsx
import { MerchantReturnPolicyJsonLd } from "next-seo";
<MerchantReturnPolicyJsonLd
applicableCountry={["DE", "AT", "CH"]}
returnPolicyCountry="IE"
returnPolicyCategory="https://schema.org/MerchantReturnFiniteReturnWindow"
merchantReturnDays={60}
itemCondition={[
"https://schema.org/NewCondition",
"https://schema.org/DamagedCondition",
]}
returnMethod={[
"https://schema.org/ReturnByMail",
"https://schema.org/ReturnInStore",
]}
returnFees="https://schema.org/ReturnShippingFees"
returnShippingFeesAmount={{
value: 2.99,
currency: "EUR",
}}
refundType={[
"https://schema.org/FullRefund",
"https://schema.org/ExchangeRefund",
]}
restockingFee={{
value: 10,
currency: "EUR",
}}
returnLabelSource="https://schema.org/ReturnLabelInBox"
// Customer remorse specific
customerRemorseReturnFees="https://schema.org/ReturnShippingFees"
customerRemorseReturnShippingFeesAmount={{
value: 5.99,
currency: "EUR",
}}
customerRemorseReturnLabelSource="https://schema.org/ReturnLabelDownloadAndPrint"
// Item defect specific
itemDefectReturnFees="https://schema.org/FreeReturn"
itemDefectReturnLabelSource="https://schema.org/ReturnLabelInBox"
// Seasonal override
returnPolicySeasonalOverride={{
startDate: "2025-12-01",
endDate: "2025-01-05",
returnPolicyCategory: "https://schema.org/MerchantReturnFiniteReturnWindow",
merchantReturnDays: 30,
}}
/>;
```
#### Product-Level Return Policy
You can also specify return policies for individual products:
```tsx
import { ProductJsonLd } from "next-seo";
<ProductJsonLd
name="Premium Wireless Headphones"
offers={{
price: 349.99,
priceCurrency: "USD",
hasMerchantReturnPolicy: {
applicableCountry: "US",
returnPolicyCategory:
"https://schema.org/MerchantReturnFiniteReturnWindow",
merchantReturnDays: 45,
returnFees: "https://schema.org/FreeReturn",
refundType: "https://schema.org/FullRefund",
},
}}
/>;
```
#### Organization-Level Return Policy
For online stores, specify a standard return policy at the organization level:
```tsx
import { OrganizationJsonLd } from "next-seo";
<OrganizationJsonLd
type="OnlineStore"
name="Example Store"
hasMerchantReturnPolicy={{
applicableCountry: ["US", "CA"],
returnPolicyCategory: "https://schema.org/MerchantReturnFiniteReturnWindow",
merchantReturnDays: 60,
returnFees: "https://schema.org/FreeReturn",
refundType: "https://schema.org/FullRefund",
}}
/>;
```
#### Props
| Property | Type | Description |
| ----------------------------------------- | ---------------------------------------- | ---------------------------------------------------------- |
| **Option A Properties** |
| `applicableCountry` | `string \| string[]` | **Required** (Option A). Countries where products are sold |
| `returnPolicyCategory` | `string` | **Required** (Option A). Type of return policy |
| `merchantReturnDays` | `number` | Days for returns (required if finite window) |
| `returnPolicyCountry` | `string \| string[]` | Countries where returns are processed |
| `returnMethod` | `string \| string[]` | How items can be returned |
| `returnFees` | `string` | Type of return fees |
| `returnShippingFeesAmount` | `MonetaryAmount` | Shipping fee for returns |
| `refundType` | `string \| string[]` | Types of refunds available |
| `restockingFee` | `number \| MonetaryAmount` | Restocking fee (percentage or fixed) |
| `returnLabelSource` | `string` | How customers get return labels |
| `itemCondition` | `string \| string[]` | Acceptable return conditions |
| **Customer Remorse Properties** |
| `customerRemorseReturnFees` | `string` | Fees for change-of-mind returns |
| `customerRemorseReturnShippingFeesAmount` | `MonetaryAmount` | Shipping fee for remorse returns |
| `customerRemorseReturnLabelSource` | `string` | Label source for remorse returns |
| **Item Defect Properties** |
| `itemDefectReturnFees` | `string` | Fees for defective item returns |
| `itemDefectReturnShippingFeesAmount` | `MonetaryAmount` | Shipping fee for defect returns |
| `itemDefectReturnLabelSource` | `string` | Label source for defect returns |
| **Seasonal Override** |
| `returnPolicySeasonalOverride` | `SeasonalOverride \| SeasonalOverride[]` | Temporary policy changes |
| **Option B Property** |
| `merchantReturnLink` | `string` | URL to return policy page |
| **Component Properties** |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key for React rendering |
#### Return Policy Categories
- `https://schema.org/MerchantReturnFiniteReturnWindow` - Limited return period
- `https://schema.org/MerchantReturnNotPermitted` - No returns allowed
- `https://schema.org/MerchantReturnUnlimitedWindow` - Unlimited return period
#### Return Methods
- `https://schema.org/ReturnByMail` - Return by mail
- `https://schema.org/ReturnInStore` - Return in store
- `https://schema.org/ReturnAtKiosk` - Return at kiosk
#### Return Fees
- `https://schema.org/FreeReturn` - No charge for returns
- `https://schema.org/ReturnFeesCustomerResponsibility` - Customer pays for return
- `https://schema.org/ReturnShippingFees` - Specific shipping fee charged
#### Refund Types
- `https://schema.org/FullRefund` - Full monetary refund
- `https://schema.org/ExchangeRefund` - Exchange for same product
- `https://schema.org/StoreCreditRefund` - Store credit issued
#### Best Practices
1. **Choose the right option**: Use Option A for detailed policies, Option B for complex or frequently changing policies
2. **Specify all countries**: List all countries where your policy applies
3. **Different return scenarios**: Use customer remorse and item defect properties for different conditions
4. **Seasonal variations**: Use seasonal overrides for holiday return windows
5. **Product overrides**: Override organization-level policies for specific products when needed
6. **Clear fee structure**: Be transparent about any fees customers will incur
7. **Multiple return methods**: Offer multiple return options for customer convenience
8. **Accurate time windows**: Ensure merchantReturnDays matches your actual policy
[↑ Back to Components](#-components-by-category)
### MovieCarouselJsonLd
The `MovieCarouselJsonLd` component helps you add structured data for movie carousels, enabling your movie lists to appear as rich results in Google Search on mobile devices. This component supports both summary page (URLs only) and all-in-one page (full movie data) patterns.
#### Basic Usage - Summary Page Pattern
Use this pattern when you have separate detail pages for each movie:
```tsx
import { MovieCarouselJsonLd } from "next-seo";
<MovieCarouselJsonLd
urls={[
"https://example.com/movies/a-star-is-born",
"https://example.com/movies/bohemian-rhapsody",
"https://example.com/movies/black-panther",
]}
/>;
```
#### All-in-One Page Pattern
Use this pattern when all movie information is on a single page:
```tsx
<MovieCarouselJsonLd
movies={[
{
name: "A Star Is Born",
image: "https://example.com/photos/6x9/star-is-born.jpg",
dateCreated: "2024-10-05",
director: "Bradley Cooper",
review: {
reviewRating: { ratingValue: 5 },
author: "John D.",
},
aggregateRating: {
ratingValue: 90,
bestRating: 100,
ratingCount: 19141,
},
},
{
name: "Bohemian Rhapsody",
image: "https://example.com/photos/6x9/bohemian.jpg",
dateCreated: "2024-11-02",
director: "Bryan Singer",
aggregateRating: {
ratingValue: 61,
bestRating: 100,
ratingCount: 21985,
},
},
]}
/>
```
#### Advanced Example with All Features
```tsx
<MovieCarouselJsonLd
movies={[
{
name: "Black Panther",
url: "https://example.com/movies/black-panther",
image: [
"https://example.com/photos/1x1/black-panther.jpg",
"https://example.com/photos/4x3/black-panther.jpg",
"https://example.com/photos/16x9/black-panther.jpg",
],
dateCreated: "2024-02-16",
director: {
name: "Ryan Coogler",
url: "https://example.com/directors/ryan-coogler",
},
review: {
reviewRating: {
ratingValue: 2,
bestRating: 5,
},
author: {
name: "Trevor R.",
url: "https://example.com/reviewers/trevor",
},
reviewBody:
"While visually stunning, the plot fell short of expectations.",
datePublished: "2024-02-20",
},
aggregateRating: {
ratingValue: 96,
bestRating: 100,
ratingCount: 88211,
},
},
]}
/>
```
#### Props
| Property | Type | Description |
| ----------- | --------------------------------------------------- | ---------------------------------------------------------------- |
| `urls` | `Array<string \| {url: string; position?: number}>` | **Required for summary pattern.** URLs to individual movie pages |
| `movies` | `MovieListItem[]` | **Required for all-in-one pattern.** Array of movie data |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key prop for React |
#### MovieListItem Properties
| Property | Type | Description |
| ----------------- | ---------------------------------------------------- | --------------------------------------------------------------- |
| `name` | `string` | **Required.** The name of the movie |
| `image` | `string \| ImageObject \| (string \| ImageObject)[]` | **Required.** Movie poster/image (6:9 aspect ratio recommended) |
| `url` | `string` | URL to the movie's page |
| `dateCreated` | `string` | Release date in ISO 8601 format |
| `director` | `string \| Person` | Movie director (accepts string or Person object) |
| `review` | `Review` | A review of the movie |
| `aggregateRating` | `AggregateRating` | Average rating based on multiple reviews |
#### Best Practices
1. **Mobile-only feature**: Movie carousels only appear on mobile devices in Google Search
2. **Image requirements**: Use 6:9 aspect ratio images (Google's requirement for movie carousels)
3. **High-quality images**: Images must be high resolution and properly formatted (.jpg, .png, .gif)
4. **Multiple images**: Consider providing multiple aspect ratios for better compatibility
5. **Complete movie data**: Include as many properties as possible for richer search results
6. **Consistent data**: All movies in the carousel must be from the same website
7. **URL structure**: For summary pages, ensure all URLs point to pages on the same domain
[↑ Back to Components](#-components-by-category)
### BreadcrumbJsonLd
The `BreadcrumbJsonLd` component helps you add breadcrumb structured data to indicate a page's position in the site hierarchy. This can help Google display breadcrumb trails in search results, making it easier for users to understand and navigate your site structure.
#### Basic Usage
```tsx
import { BreadcrumbJsonLd } from "next-seo";
export default function ProductPage() {
return (
<>
<BreadcrumbJsonLd
items={[
{
name: "Home",
item: "https://example.com",
},
{
name: "Products",
item: "https://example.com/products",
},
{
name: "Electronics",
item: "https://example.com/products/electronics",
},
{
name: "Headphones",
item: "https://example.com/products/electronics/headphones",
},
{
name: "Wireless Headphones XYZ",
},
]}
/>
<main>
<h1>Wireless Headphones XYZ</h1>
{/* Product content */}
</main>
</>
);
}
```
#### Multiple Breadcrumb Trails
Some pages can be reached through multiple paths. You can specify multiple breadcrumb trails:
```tsx
<BreadcrumbJsonLd
multipleTrails={[
// First trail: Category path
[
{
name: "Books",
item: "https://example.com/books",
},
{
name: "Science Fiction",
item: "https://example.com/books/sciencefiction",
},
{
name: "Award Winners",
},
],
// Second trail: Award path
[
{
name: "Literature",
item: "https://example.com/literature",
},
{
name: "Award Winners",
},
],
]}
/>
```
#### Advanced Example with Thing Objects
You can use Thing objects with `@id` instead of plain URL strings:
```tsx
<BreadcrumbJsonLd
items={[
{
name: "Home",
item: "https://example.com",
},
{
name: "Blog",
item: { "@id": "https://example.com/blog" },
},
{
name: "Technology",
item: { "@id": "https://example.com/blog/technology" },
},
{
name: "Understanding JSON-LD",
},
]}
scriptId="blog-breadcrumb"
scriptKey="blog-breadcrumb-key"
/>
```
#### Props
| Property | Type | Description |
| ---------------- | ------------------------ | ---------------------------------------------------------------- |
| `items` | `BreadcrumbListItem[]` | Array of breadcrumb items (required if not using multipleTrails) |
| `multipleTrails` | `BreadcrumbListItem[][]` | Array of breadcrumb trails (required if not using items) |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key prop for React |
**BreadcrumbListItem Type:**
| Property | Type | Description |
| -------- | ----------------------------- | ------------------------------------------------------ |
| `name` | `string` | **Required.** The title of the breadcrumb |
| `item` | `string \| { "@id": string }` | URL or Thing object (optional for the last breadcrumb) |
#### Best Practices
1. **Omit the last item's URL**: The last breadcrumb (current page) typically shouldn't have an `item` property
2. **Use logical hierarchy**: Breadcrumbs should represent a typical user path, not necessarily mirror URL structure
3. **Keep names concise**: Use clear, descriptive names that help users understand the hierarchy
4. **Multiple trails**: Use `multipleTrails` when a page can be logically reached through different paths
5. **Include home**: Start trails from a logical entry point (often "Home") but it's not required
6. **Avoid duplicates**: Each trail should represent a unique path to the page
7. **Match visual breadcrumbs**: The structured data should match the breadcrumbs shown on your page
[↑ Back to Components](#-components-by-category)
### CarouselJsonLd
The `CarouselJsonLd` component helps you add structured data for carousels (ItemList) to enable rich results that display multiple cards from your site in a carousel format. This component supports Course, Movie, Recipe, and Restaurant content types.
#### Basic Usage
**Summary Page Pattern (URLs only):**
```tsx
import { CarouselJsonLd } from "next-seo";
// Simple array of URLs
<CarouselJsonLd
urls={[
"https://example.com/recipe/cookies",
"https://example.com/recipe/cake",
"https://example.com/recipe/pie"
]}
/>
// With custom positions
<CarouselJsonLd
urls={[
{ url: "https://example.com/movie/matrix", position: 1 },
"https://example.com/movie/inception", // position will be 2
{ url: "https://example.com/movie/interstellar", position: 3 }
]}
/>
```
**All-in-One Page Pattern (Full Data):**
```tsx
import { CarouselJsonLd } from "next-seo";
// Course Carousel
<CarouselJsonLd
contentType="Course"
items={[
{
name: "Introduction to React",
description: "Learn the fundamentals of React",
url: "https://example.com/courses/react",
provider: "Tech Academy"
},
{
name: "Advanced TypeScript",
description: "Master TypeScript features",
provider: {
name: "Code School",
url: "https://example.com/school"
}
}
]}
/>
// Movie Carousel
<CarouselJsonLd
contentType="Movie"
items={[
{
name: "The Matrix",
image: "https://example.com/matrix.jpg",
director: "The Wachowskis",
dateCreated: "1999-03-31",
aggregateRating: {
ratingValue: 8.7,
ratingCount: 1000
}
},
{
name: "Inception",
image: [
"https://example.com/inception1.jpg",
"https://example.com/inception2.jpg"
],
director: { name: "Christopher Nolan" }
}
]}
/>
// Recipe Carousel
<CarouselJsonLd
contentType="Recipe"
items={[
{
name: "Chocolate Chip Cookies",
image: "https://example.com/cookies.jpg",
description: "Classic chocolate chip cookies",
author: "Chef John",
prepTime: "PT20M",
cookTime: "PT12M",
recipeYield: 24,
recipeIngredient: [
"2 cups flour",
"1 cup butter",
"1 cup chocolate chips"
],
aggregateRating: {
ratingValue: 4.8,
ratingCount: 250
}
}
]}
/>
// Restaurant Carousel
<CarouselJsonLd
contentType="Restaurant"
items={[
{
name: "Joe's Pizza",
address: "123 Main St, New York, NY 10001",
telephone: "+1-212-555-0100",
servesCuisine: ["Italian", "Pizza"],
priceRange: "$$",
aggregateRating: {
ratingValue: 4.5,
ratingCount: 500
},
geo: {
latitude: 40.7128,
longitude: -74.0060
}
}
]}
/>
```
#### Advanced Examples
**Recipe Carousel with Full Details:**
```tsx
<CarouselJsonLd
contentType="Recipe"
items={[
{
name: "Perfect Pancakes",
image: [
"https://example.com/pancakes1.jpg",
"https://example.com/pancakes2.jpg",
],
description: "Fluffy and delicious pancakes",
author: [
"Chef Alice",
{ name: "Chef Bob", url: "https://example.com/chefs/bob" },
],
datePublished: "2024-01-01",
prepTime: "PT10M",
cookTime: "PT15M",
totalTime: "PT25M",
recipeYield: "4 servings",
recipeCategory: "Breakfast",
recipeCuisine: "American",
recipeIngredient: [
"2 cups all-purpose flour",
"2 tablespoons sugar",
"2 eggs",
"1 1/2 cups milk",
],
recipeInstructions: [
"Mix dry ingredients in a bowl",
{ text: "Whisk wet ingredients separately" },
{
name: "Cooking",
itemListElement: [
{ text: "Heat griddle to 375°F" },
{ text: "Pour batter and cook until bubbles form" },
{ text: "Flip and cook until golden" },
],
},
],
nutrition: {
calories: "250 calories",
proteinContent: "8g",
carbohydrateContent: "35g",
fatContent: "9g",
},
aggregateRating: {
ratingValue: 4.9,
ratingCount: 1200,
},
video: {
name: "How to Make Perfect Pancakes",
description: "Step-by-step video guide",
thumbnailUrl: "https://example.com/pancakes-thumb.jpg",
contentUrl: "https://example.com/pancakes-video.mp4",
uploadDate: "2024-01-01",
duration: "PT5M30S",
},
keywords: "pancakes, breakfast, easy recipe",
},
]}
/>
```
**Restaurant Carousel with Opening Hours:**
```tsx
<CarouselJsonLd
contentType="Restaurant"
items={[
{
name: "Fine Dining Restaurant",
address: {
streetAddress: "456 Oak Avenue",
addressLocality: "San Francisco",
addressRegion: "CA",
postalCode: "94102",
addressCountry: "US",
},
image: [
"https://example.com/restaurant1.jpg",
"https://example.com/restaurant2.jpg",
],
telephone: "+1-415-555-0200",
url: "https://example.com/restaurant",
menu: "https://example.com/restaurant/menu",
servesCuisine: ["French", "Mediterranean"],
priceRange: "$$$",
openingHoursSpecification: [
{
dayOfWeek: ["Monday", "Tuesday", "Wednesday", "Thursday"],
opens: "17:00",
closes: "22:00",
},
{
dayOfWeek: ["Friday", "Saturday"],
opens: "17:00",
closes: "23:00",
},
],
review: [
{
reviewRating: { ratingValue: 5 },
author: "Food Critic",
reviewBody: "Exceptional dining experience",
},
],
aggregateRating: {
ratingValue: 4.7,
bestRating: 5,
ratingCount: 850,
},
},
]}
/>
```
#### Props
| Property | Type | Description |
| ------------- | ----------------------------------------------------------------- | ------------------------------------------------ |
| `urls` | `SummaryPageItem[]` | Array of URLs for summary page pattern |
| `contentType` | `"Course" \| "Movie" \| "Recipe" \| "Restaurant"` | Type of content in the carousel (for all-in-one) |
| `items` | `CourseItem[] \| MovieItem[] \| RecipeItem[] \| RestaurantItem[]` | Array of items matching the content type |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key prop for React |
**SummaryPageItem Type:**
| Type | Description |
| ------------------------------------ | --------------------------------- |
| `string` | Simple URL string |
| `{ url: string; position?: number }` | URL with optional custom position |
#### Best Practices
1. **Choose the right pattern**:
- Use **summary page pattern** when you have separate detail pages for each item
- Use **all-in-one pattern** when all content is on a single page
2. **Consistent content types**: All items in a carousel must be of the same type (e.g., all recipes or all movies)
3. **Required images**:
- Movies require at least one image
- Recipes should include images for better visibility
- Use multiple aspect ratios when possible
4. **Position numbering**:
- Positions start at 1, not 0
- If not specified, positions are auto-assigned sequentially
5. **URL structure**: For summary pages, ensure all URLs point to pages on the same domain
6. **Rich content**: Include as much relevant information as possible for better search results
7. **Validation**: Test your structured data with Google's Rich Results Test
[↑ Back to Components](#-components-by-category)
### CourseJsonLd
The `CourseJsonLd` component helps you add structured data for courses to enable course list rich results in Google Search. This can help prospective students discover your courses more easily.
#### Basic Usage
**Single Course:**
```tsx
import { CourseJsonLd } from "next-seo";
<CourseJsonLd
name="Introduction to Computer Science"
description="An introductory CS course laying out the basics."
provider="University of Technology"
/>;
```
**Course List:**
```tsx
import { CourseJsonLd } from "next-seo";
// Summary page pattern - just URLs
<CourseJsonLd
type="list"
urls={[
"https://example.com/courses/intro-cs",
"https://example.com/courses/intermediate-cs",
"https://example.com/courses/advanced-cs"
]}
/>
// All-in-one page pattern - full course data
<CourseJsonLd
type="list"
courses={[
{
name: "Introduction to Programming",
description: "Learn the basics of programming.",
url: "https://example.com/courses/intro-programming",
provider: "Tech Institute"
},
{
name: "Advanced Algorithms",
description: "Study complex algorithmic solutions.",
provider: {
name: "University Online",
sameAs: "https://university.edu"
}
}
]}
/>
```
#### Props
**Single Course Props:**
| Property | Type | Description |
| ------------- | ------------------------------------------------------- | --------------------------------------------------------------------- |
| `type` | `"single"` | Optional. Explicitly sets single course pattern |
| `name` | `string` | **Required.** The title of the course |
| `description` | `string` | **Required.** A description of the course (60 char limit for display) |
| `url` | `string` | The URL of the course page |
| `provider` | `string \| Organization \| Omit<Organization, "@type">` | The organization offering the course |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key for React reconciliation |
**Course List Props:**
| Property | Type | Description |
| ----------- | -------------------------------------------------- | ------------------------------------------ |
| `type` | `"list"` | **Required.** Sets the course list pattern |
| `urls` | `(string \| { url: string; position?: number })[]` | URLs for summary page pattern |
| `courses` | `CourseListItem[]` | Full course data for all-in-one pattern |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key for React reconciliation |
#### Advanced Example
```tsx
import { CourseJsonLd } from "next-seo";
export default function CourseCatalogPage() {
return (
<>
<CourseJsonLd
type="list"
courses={[
{
name: "Full-Stack Web Development",
description: "Master modern web development from front to back.",
url: "https://example.com/courses/fullstack",
provider: {
name: "Code Academy",
url: "https://codeacademy.com",
sameAs: [
"https://twitter.com/codeacademy",
"https://linkedin.com/company/codeacademy",
],
},
},
{
name: "Data Science with Python",
description:
"Learn data analysis and machine learning with Python.",
url: "https://example.com/courses/data-science",
provider: "Tech University",
},
{
name: "Mobile App Development",
description: "Build iOS and Android apps with React Native.",
url: "https://example.com/courses/mobile-dev",
provider: {
name: "Mobile Dev Institute",
logo: "https://example.com/logo.png",
},
},
]}
/>
<h1>Our Course Catalog</h1>
{/* Your course list UI */}
</>
);
}
```
#### Best Practices
1. **Minimum of 3 courses**: Google requires at least 3 courses for course list rich results
2. **Consistent provider**: Use the same format for provider across all courses
3. **Description length**: Keep descriptions under 60 characters for optimal display
4. **Valid URLs**: Ensure all course URLs are accessible and on the same domain
5. **Choose the right pattern**:
- Use **summary page** pattern when courses have their own detail pages
- Use **all-in-one** pattern when all course information is on a single page
6. **Avoid promotional content**: Don't include prices, discounts, or marketing language in course names
### EventJsonLd
The `EventJsonLd` component helps you add structured data for events to improve their discoverability in Google Search results and other Google products like Google Maps. Events can appear with rich features including images, dates, locations, and ticket information.
#### Basic Usage
```tsx
import { EventJsonLd } from "next-seo";
<EventJsonLd
name="The Adventures of Kira and Morrison"
startDate="2025-07-21T19:00-05:00"
location="Snickerpark Stadium"
/>;
```
#### Standard Event Example
```tsx
<EventJsonLd
name="The Adventures of Kira and Morrison"
startDate="2025-07-21T19:00-05:00"
endDate="2025-07-21T23:00-05:00"
location={{
"@type": "Place",
name: "Snickerpark Stadium",
address: {
"@type": "PostalAddress",
streetAddress: "100 West Snickerpark Dr",
addressLocality: "Snickertown",
postalCode: "19019",
addressRegion: "PA",
addressCountry: "US",
},
}}
description="The Adventures of Kira and Morrison is coming to Snickertown in a can't miss performance."
image={[
"https://example.com/photos/1x1/photo.jpg",
"https://example.com/photos/4x3/photo.jpg",
"https://example.com/photos/16x9/photo.jpg",
]}
offers={{
"@type": "Offer",
url: "https://www.example.com/event_offer/12345_202403180430",
price: 30,
priceCurrency: "USD",
availability: "https://schema.org/InStock",
validFrom: "2024-05-21T12:00",
}}
performer={{
"@type": "PerformingGroup",
name: "Kira and Morrison",
}}
organizer={{
"@type": "Organization",
name: "Kira and Morrison Music",
url: "https://kiraandmorrisonmusic.com",
}}
/>
```
#### Event Status Examples
##### Cancelled Event
```tsx
<EventJsonLd
name="Summer Festival 2025"
startDate="2025-08-15T12:00:00"
location="City Park"
eventStatus="https://schema.org/EventCancelled"
/>
```
##### Rescheduled Event
```tsx
<EventJsonLd
name="Tech Conference 2025"
startDate="2025-09-20T09:00:00"
location="Convention Center"
eventStatus="https://schema.org/EventRescheduled"
previousStartDate="2025-07-15T09:00:00"
/>
```
#### Props
| Property | Type | Description |
| ------------------- | ---------------------------------------------------- | ---------------------------------------------------------------------------------- |
| `name` | `string` | **Required.** The full title of the event |
| `startDate` | `string` | **Required.** Start date/time in ISO-8601 format |
| `location` | `string \| Place` | **Required.** Event venue (string or Place object) |
| `endDate` | `string` | End date/time in ISO-8601 format |
| `description` | `string` | Detailed description of the event |
| `eventStatus` | `EventStatusType` | Status: EventScheduled (default), EventCancelled, EventPostponed, EventRescheduled |
| `image` | `string \| ImageObject \| (string \| ImageObject)[]` | Event images (recommended: multiple aspect ratios) |
| `offers` | `Offer \| Offer[]` | Ticket/pricing information |
| `performer` | `string \| Person \| PerformingGroup \| array` | Performers at the event |
| `organizer` | `string \| Person \| Organization` | Event host/organizer |
| `previousStartDate` | `string \| string[]` | Previous date(s) for rescheduled events |
| `url` | `string` | URL of the event page |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key prop for React |
#### Offer Type
| Property | Type | Description |
| --------------- | -------- | ------------------------------------------------ |
| `url` | `string` | URL to purchase tickets |
| `price` | `number` | Lowest available price (use 0 for free events) |
| `priceCurrency` | `string` | 3-letter ISO 4217 currency code (e.g., "USD") |
| `availability` | `string` | Availability status (InStock, SoldOut, PreOrder) |
| `validFrom` | `string` | Date/time when tickets go on sale |
#### Best Practices
1. **Date/Time Format**: Always use ISO-8601 format with timezone offset (e.g., `2025-07-21T19:00-05:00`)
2. **Day-long Events**: For all-day events, use date only format (e.g., `2025-07-04`)
3. **Location Details**: Provide complete address information for better discoverability
4. **Multiple Images**: Include images in different aspect ratios (16:9, 4:3, 1:1) for various display contexts
5. **Event Status**: Keep original dates when cancelling/postponing; only update the `eventStatus`
6. **Free Events**: Set `price: 0` for events without charge
7. **Multiple Performers**: Use an array when listing multiple artists or speakers
8. **Rescheduled Events**: Always include `previousStartDate` when using `EventRescheduled` status
#### Date and Time Guidelines
- **Include timezone**: Specify UTC/GMT offset (e.g., `-05:00` for EST)
- **Multi-day events**: Set both `startDate` and `endDate`
- **Unknown end time**: Omit `endDate` rather than guessing
- **Date-only format**: Use for all-day events (e.g., festivals)
Example timezone handling:
```tsx
// New York event during standard time
startDate: "2025-12-21T19:00:00-05:00";
// California event during daylight saving time
startDate: "2025-07-21T19:00:00-07:00";
// All-day event
startDate: "2025-07-04";
endDate: "2025-07-04";
```
### FAQJsonLd
The `FAQJsonLd` component helps you add structured data for frequently asked questions (FAQ) pages. This can help your FAQ content appear as rich results in Google Search, making it easier for users to find answers to common questions.
> **Note**: FAQ rich results are only available for well-known, authoritative government or health websites. However, implementing proper FAQ structured data is still valuable for SEO and can help search engines better understand your content.
#### Basic Usage
```tsx
import { FAQJsonLd } from "next-seo";
export default function FAQPage() {
return (
<>
<FAQJsonLd
questions={[
{
question: "How to find an apprenticeship?",
answer:
"We provide an official service to search through available apprenticeships. To get started, create an account here, specify the desired region, and your preferences.",
},
{
question: "Whom to contact?",
answer:
"You can contact the apprenticeship office through our official phone hotline above, or with the web-form below.",
},
]}
/>
<h1>Frequently Asked Questions</h1>
{/* Your FAQ content */}
</>
);
}
```
#### Advanced Example with HTML Content
FAQ answers support HTML content including links, lists, and formatting:
```tsx
<FAQJsonLd
questions={[
{
question: "What documents are required for application?",
answer: `
<p>You'll need to provide the following documents:</p>
<ul>
<li>Valid government-issued ID</li>
<li>High school diploma or equivalent</li>
<li>Proof of residence</li>
<li><a href="/forms/medical">Medical clearance form</a></li>
</ul>
<p>All documents must be submitted within 30 days of application.</p>
`,
},
{
question: "How long does the application process take?",
answer:
"<p>The typical processing time is <strong>7-10 business days</strong> from the date we receive all required documents.</p>",
},
]}
scriptId="faq-structured-data"
/>
```
#### Different Input Formats
The component supports multiple input formats for flexibility:
```tsx
// Simple question/answer format (recommended)
<FAQJsonLd
questions={[
{
question: "What is the cost?",
answer: "The program is free for eligible participants.",
},
]}
/>
// Schema.org name/acceptedAnswer format
<FAQJsonLd
questions={[
{
name: "What is the cost?",
acceptedAnswer: "The program is free for eligible participants.",
},
]}
/>
// With Answer object
<FAQJsonLd
questions={[
{
name: "What is the cost?",
acceptedAnswer: {
"@type": "Answer",
text: "The program is free for eligible participants.",
},
},
]}
/>
```
#### Props
| Property | Type | Description |
| ----------- | ----------------- | --------------------------------------------------------------------------------------- |
| `questions` | `QuestionInput[]` | **Required.** Array of questions and answers. See input formats below. |
| `scriptId` | `string` | Optional. Sets the `id` attribute on the script tag. |
| `scriptKey` | `string` | Optional. Sets the `data-testid` attribute on the script tag. Defaults to "faq-jsonld". |
#### Question Input Formats
The `questions` array accepts several formats:
1. **Simple object** (recommended):
```tsx
{ question: "string", answer: "string" }
```
2. **Schema.org format**:
```tsx
{ name: "string", acceptedAnswer: "string" }
```
3. **Full Answer object**:
```tsx
{
name: "string",
acceptedAnswer: {
"@type": "Answer",
text: "string"
}
}
```
#### Best Practices
1. **Include complete Q&A**: Each question and answer should contain the full text that appears on your page
2. **Use HTML wisely**: Google supports these HTML tags in answers: `<h1>` through `<h6>`, `<br>`, `<ol>`, `<ul>`, `<li>`, `<a>`, `<p>`, `<div>`, `<b>`, `<strong>`, `<i>`, and `<em>`
3. **Match page content**: The FAQ structured data must match the visible Q&A content on your page
4. **Avoid promotional content**: Don't use FAQPage for advertising purposes
5. **One instance per page**: If the same FAQ appears on multiple pages, only mark it up on one page
6. **Expandable sections**: It's fine if answers are hidden behind expandable sections, as long as users can access them
7. **No user submissions**: FAQPage is for questions with single, authoritative answers. For user-generated Q&A, use QAPage instead
### ImageJsonLd
The `ImageJsonLd` component helps you add structured data for images to improve their appearance in Google Images. This enables features like the Licensable badge and displays metadata such as creator, credit, copyright, and licensing information.
#### Basic Usage
```tsx
import { ImageJsonLd } from "next-seo";
<ImageJsonLd
contentUrl="https://example.com/photos/black-labrador-puppy.jpg"
creator="Brixton Brownstone"
license="https://example.com/license"
acquireLicensePage="https://example.com/how-to-use-my-images"
creditText="Labrador PhotoLab"
copyrightNotice="Clara Kent"
/>;
```
#### Advanced Usage - Organization Creator
```tsx
<ImageJsonLd
contentUrl="https://example.com/photos/product-photo.jpg"
creator={{
name: "PhotoLab Studios",
logo: "https://example.com/photolab-logo.jpg",
sameAs: ["https://twitter.com/photolab", "https://instagram.com/photolab"],
}}
license="https://creativecommons.org/licenses/by-nc/4.0/"
acquireLicensePage="https://example.com/licensing"
creditText="PhotoLab Studios"
copyrightNotice="© 2024 PhotoLab Studios"
/>
```
#### Multiple Images
```tsx
<ImageJsonLd
images={[
{
contentUrl: "https://example.com/photos/black-labrador-puppy.jpg",
creator: "Brixton Brownstone",
license: "https://example.com/license",
creditText: "Labrador PhotoLab",
},
{
contentUrl: "https://example.com/photos/adult-black-labrador.jpg",
creator: [
"Brixton Brownstone",
{
name: "Clara Kent",
url: "https://clarakent.com",
},
],
copyrightNotice: "© 2024 Clara Kent",
license: "https://example.com/license",
},
]}
/>
```
#### Props
| Property | Type | Description |
| -------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `contentUrl` | `string` | **Required.** The URL of the actual image content |
| `creator` | `Author \| Author[]` | The creator(s) of the image (photographer, designer, etc.). Can be string name(s), Person, or Organization object(s) |
| `creditText` | `string` | The name of the person/organization credited when the image is published |
| `copyrightNotice` | `string` | The copyright notice for claiming intellectual property |
| `license` | `string` | URL to a page describing the license governing the image's use |
| `acquireLicensePage` | `string` | URL to a page where users can find information on how to license the image |
| `images` | `Array<ImageObject>` | Array of image objects with the above properties (for multiple images) |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key for script deduplication |
> **Note**: You must include `contentUrl` and at least one of: `creator`, `creditText`, `copyrightNotice`, or `license` for the image to be eligible for enhancements like the Licensable badge.
#### Best Practices
1. **Always provide licensing information**: Include the `license` property to make your images eligible for the Licensable badge
2. **Credit creators properly**: Use structured creator information to ensure proper attribution
3. **Include acquire license page**: Help users understand how they can legally use your images
4. **Use consistent copyright notices**: Maintain clear copyright information across your images
5. **Multiple creators**: When multiple people contributed to an image, list all creators
6. **Organization vs Person**: Use Organization type for companies/studios, Person type for individuals
### QuizJsonLd
The `QuizJsonLd` component helps you add structured data for educational quizzes and flashcards. This can help your educational content appear in Google's education Q&A carousel when users search for educational topics.
#### Basic Usage
```tsx
import { QuizJsonLd } from "next-seo";
export default function BiologyQuizPage() {
return (
<>
<QuizJsonLd
questions={[
{
question: "What is the powerhouse of the cell?",
answer: "Mitochondria",
},
{
question:
"What process do plants use to convert sunlight into energy?",
answer: "Photosynthesis",
},
]}
about="Cell Biology"
educationalAlignment={[
{
type: "educationalSubject",
name: "Biology",
},
{
type: "educationalLevel",
name: "Grade 10",
},
]}
/>
{/* Your quiz content */}
</>
);
}
```
#### Props
| Property | Type | Description |
| ---------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------ |
| `questions` | `QuestionInput[]` | **Required.** Array of flashcard questions and answers |
| `about` | `string \| Thing` | The subject or topic of the quiz |
| `educationalAlignment` | `Array<{type: "educationalSubject" \| "educationalLevel", name: string}>` | Educational alignments specifying subject and/or grade level |
| `scriptId` | `string` | Custom ID for the script tag |
| `scriptKey` | `string` | Custom key for React (defaults to "quiz-jsonld") |
#### Question Formats
The `questions` array accepts several formats:
1. **Simple object** (recommended):
```tsx
{ question: "What is 2 + 2?", answer: "4" }
```
2. **String format** (for fact-based flashcards):
```tsx
"The Earth revolves around the Sun in 365.25 days";
```
3. **Text/acceptedAnswer format**:
```tsx
{ text: "What is DNA?", acceptedAnswer: "Deoxyribonucleic acid" }
```
4. **Full Answer object**:
```tsx
{
text: "Explain photosynthesis",
acceptedAnswer: {
"@type": "Answer",
text: "The process by which plants convert light energy into chemical energy"
}
}
```
#### Advanced Example
```tsx
<QuizJsonLd
questions={[
// Simple flashcard fact
"The mitochondria is the powerhouse of the cell",
// Question/answer format
{
question: "What are the four bases of DNA?",
answer: "Adenine (A), Thymine (T), Guanine (G), and Cytosine (C)",
},
// Full format with Answer object
{
text: "Describe the water cycle",
acceptedAnswer: {
"@type": "Answer",
text: "The continuous movement of water through evaporation, condensation, precipitation, and collection",
},
},
]}
about={{
name: "Biology Fundamentals",
description: "Core concepts in cellular and molecular biology",
url: "https://example.com/biology-course",
}}
educationalAlignment={[
{
type: "educationalSubject",
name: "Biology",
},
{
type: "educationalLevel",
name: "High School",
},
]}
/>
```
#### Best Practices
1. **Educational content only**: Quiz structured data is specifically for educational flashcards and Q&A
2. **Use "Flashcard" type**: All questions automatically use `eduQuestionType: "Flashcard"` as required by Google
3. **Clear answers**: Provide concise, factual answers appropriate for the educational level
4. **Subject alignment**: Always specify the educational subject using `educationalAlignment`
5. **Grade level**: Include the target grade or educational level when applicable
6. **Match visible content**: The structured data should match the quiz content displayed on your page
7. **Single answer format**: Each question should have one clear, authoritative answer
> **Note**: The education Q&A carousel is available when searching for education-related topics in English, Portuguese, Spanish (Mexico), and Vietnamese.
### DatasetJsonLd
The `DatasetJsonLd` component helps you add structured data for datasets, making them easier to find in Google's Dataset Search. This is ideal for scientific data, government data, machine learning datasets, and any other structured data collections.
#### Basic Usage
```tsx
import { DatasetJsonLd } from "next-seo";
export default function DatasetPage() {
return (
<>
<DatasetJsonLd
name="NCDC Storm Events Database"
description="Storm Data is provided by the National Weather Service (NWS) and contain statistics on personal injuries and damage estimates."
url="https://example.com/dataset/storm-events"
creator="NOAA"
distribution={{
contentUrl: "https://www.ncdc.noaa.gov/stormevents/ftp.jsp",
encodingFormat: "CSV",
}}
/>
{/* Your dataset page content */}
</>
);
}
```
#### Advanced Example with Full Features
```tsx
<DatasetJsonLd
name="Global Climate Data 2020-2024"
description="Comprehensive climate measurements including temperature, precipitation, and atmospheric data collected from weather stations worldwide"
url="https://example.com/datasets/global-climate-2020-2024"
sameAs={[
"https://doi.org/10.1000/182",
"https://data.gov/dataset/climate-2020-2024",
]}
identifier={[
"https://doi.org/10.1000/182",
{
value: "ark:/12345/fk1234",
propertyID: "ARK",
},
]}
keywords={[
"climate",
"temperature",
"precipitation",
"weather",
"atmospheric data",
]}
license="https://creativecommons.org/publicdomain/zero/1.0/"
isAccessibleForFree={true}
creator={[
{
name: "National Centers for Environmental Information",
url: "https://www.ncei.noaa.gov/",
contactPoint: {
contactType: "customer service",
telephone: "+1-828-271-4800",
email: "ncei.orders@noaa.gov",
},
},
"Dr. Jane Smith",
]}
funder={{
name: "National Science Foundation",
sameAs: "https://ror.org/021nxhr62",
}}
includedInDataCatalog={{
name: "data.gov",
url: "https://data.gov",
}}
distribution={[
{
contentUrl: "https://example.com/data/climate-2020-2024.csv",
encodingFormat: "CSV",
contentSize: "2.5GB",
description: "Complete dataset in CSV format",
},
{
contentUrl: "https://example.com/data/climate-2020-2024.json",
encodingFormat: "JSON",
contentSize: "3.1GB",
description: "Complete dataset in JSON format",
},
]}
temporalCoverage="2020-01-01/2024-12-31"
spatialCoverage={{
name: "Global",
geo: {
box: "-90 -180 90 180",
},
}}
measurementTechnique="Satellite observation and ground station measurements"
variableMeasured={[
"temperature",
"precipitation",
{
name: "Atmospheric Pressure",
value: "hectopascals",
},
]}
version="2.1"
/>
```
#### Props
| Property | Type | Description |
| ----------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
| `name` | `string` | **Required.** A descriptive name of the dataset |
| `description` | `string` | **Required.** A short summary describing the dataset (50-5000 characters) |
| `url` | `string` | URL of the dataset landing page |
| `sameAs` | `string \| string[]` | URLs of pages that unambiguously indicate the dataset's identity |
| `identifier` | `string \| PropertyValue \| (string \| PropertyValue)[]` | Identifiers such as DOI or Compact Identifiers |
| `keywords` | `string \| string[]` | Keywords summarizing the dataset |
| `license` | `string \| CreativeWork` | License under which the dataset is distributed |
| `isAccessibleForFree` | `boolean` | Whether the dataset is accessible without payment |
| `hasPart` | `Dataset \| Dataset[]` | Smaller datasets that are part of this dataset |
| `isPartOf` | `string \| Dataset` | A larger dataset that this dataset is part of |
| `creator` | `string \| Person \| Organization \| (string \| Person \| Organization)[]` | The creator or author of the dataset |
| `funder` | `string \| Person \| Organization \| (string \| Person \| Organization)[]` | Person or organization that provides financial support |
| `includedInDataCatalog` | `DataCatalog` | The catalog to which the dataset belongs |
| `distribution` | `DataDownload \| DataDownload[]` | Download locations and formats for the dataset |
| `temporalCoverage` | `string` | Time interval covered by the dataset (ISO 8601 fo
gitextract_boozz7yy/ ├── .changeset/ │ ├── README.md │ └── config.json ├── .editorconfig ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── dependabot.yml │ ├── pull_request_template.md │ └── workflows/ │ ├── changeset-check.yml │ ├── changesets.yml │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .husky/ │ └── pre-commit ├── .npmignore ├── .prettierignore ├── .vscode/ │ └── settings.json ├── ADDING_NEW_COMPONENTS.md ├── AGENTS.md ├── CHANGELOG.md ├── CLAUDE.md ├── CONTRIBUTING.md ├── CUSTOM_COMPONENTS.md ├── LICENSE ├── LICENSE.md ├── LIST.md ├── README.md ├── eslint.config.mjs ├── examples/ │ └── app-router-showcase/ │ ├── .gitignore │ ├── CLAUDE.md │ ├── README.md │ ├── app/ │ │ ├── aggregate-rating/ │ │ │ └── page.tsx │ │ ├── aggregate-rating-restaurant/ │ │ │ └── page.tsx │ │ ├── article/ │ │ │ └── page.tsx │ │ ├── blog-posting/ │ │ │ └── page.tsx │ │ ├── breadcrumb/ │ │ │ ├── advanced/ │ │ │ │ └── page.tsx │ │ │ ├── multiple/ │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ ├── carousel-course/ │ │ │ └── page.tsx │ │ ├── carousel-movie/ │ │ │ └── page.tsx │ │ ├── carousel-recipe/ │ │ │ └── page.tsx │ │ ├── carousel-restaurant/ │ │ │ └── page.tsx │ │ ├── carousel-summary/ │ │ │ └── page.tsx │ │ ├── claim-review/ │ │ │ └── page.tsx │ │ ├── claim-review-advanced/ │ │ │ └── page.tsx │ │ ├── claim-review-organization/ │ │ │ └── page.tsx │ │ ├── course/ │ │ │ └── page.tsx │ │ ├── course-list/ │ │ │ └── page.tsx │ │ ├── course-list-summary/ │ │ │ └── page.tsx │ │ ├── creative-work/ │ │ │ └── page.tsx │ │ ├── creative-work-blog/ │ │ │ └── page.tsx │ │ ├── creative-work-multiple/ │ │ │ └── page.tsx │ │ ├── creative-work-news/ │ │ │ └── page.tsx │ │ ├── custom-podcast/ │ │ │ └── page.tsx │ │ ├── custom-service/ │ │ │ └── page.tsx │ │ ├── dataset/ │ │ │ └── page.tsx │ │ ├── dataset-advanced/ │ │ │ └── page.tsx │ │ ├── dataset-catalog/ │ │ │ └── page.tsx │ │ ├── dataset-nested/ │ │ │ └── page.tsx │ │ ├── discussion-forum/ │ │ │ └── page.tsx │ │ ├── discussion-forum-advanced/ │ │ │ └── page.tsx │ │ ├── discussion-forum-deleted/ │ │ │ └── page.tsx │ │ ├── employer-aggregate-rating/ │ │ │ └── page.tsx │ │ ├── employer-aggregate-rating-advanced/ │ │ │ └── page.tsx │ │ ├── employer-aggregate-rating-custom-scale/ │ │ │ └── page.tsx │ │ ├── event/ │ │ │ └── page.tsx │ │ ├── event-cancelled/ │ │ │ └── page.tsx │ │ ├── event-free/ │ │ │ └── page.tsx │ │ ├── event-rescheduled/ │ │ │ └── page.tsx │ │ ├── faq/ │ │ │ └── page.tsx │ │ ├── faq-advanced/ │ │ │ └── page.tsx │ │ ├── faq-health/ │ │ │ └── page.tsx │ │ ├── globals.css │ │ ├── howto/ │ │ │ └── page.tsx │ │ ├── howto-advanced/ │ │ │ └── page.tsx │ │ ├── image/ │ │ │ └── page.tsx │ │ ├── image-advanced/ │ │ │ └── page.tsx │ │ ├── image-multiple/ │ │ │ └── page.tsx │ │ ├── job-posting/ │ │ │ └── page.tsx │ │ ├── job-posting-advanced/ │ │ │ └── page.tsx │ │ ├── job-posting-remote/ │ │ │ └── page.tsx │ │ ├── jsonld-test-page/ │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── local-business/ │ │ │ └── page.tsx │ │ ├── merchant-return-policy/ │ │ │ └── page.tsx │ │ ├── merchant-return-policy-advanced/ │ │ │ └── page.tsx │ │ ├── merchant-return-policy-link/ │ │ │ └── page.tsx │ │ ├── mobile-app/ │ │ │ └── page.tsx │ │ ├── movie-carousel/ │ │ │ └── page.tsx │ │ ├── movie-carousel-advanced/ │ │ │ └── page.tsx │ │ ├── movie-carousel-summary/ │ │ │ └── page.tsx │ │ ├── news-article/ │ │ │ └── page.tsx │ │ ├── online-store/ │ │ │ └── page.tsx │ │ ├── online-store-loyalty/ │ │ │ └── page.tsx │ │ ├── organization/ │ │ │ └── page.tsx │ │ ├── organization-advanced/ │ │ │ └── page.tsx │ │ ├── organization-reviews/ │ │ │ └── page.tsx │ │ ├── page.module.css │ │ ├── page.tsx │ │ ├── product/ │ │ │ └── page.tsx │ │ ├── product-3d-model/ │ │ │ └── page.tsx │ │ ├── product-aggregate/ │ │ │ └── page.tsx │ │ ├── product-certification/ │ │ │ └── page.tsx │ │ ├── product-member-pricing/ │ │ │ └── page.tsx │ │ ├── product-review/ │ │ │ └── page.tsx │ │ ├── product-sale-pricing/ │ │ │ └── page.tsx │ │ ├── product-shipping-options/ │ │ │ └── page.tsx │ │ ├── product-unit-pricing/ │ │ │ └── page.tsx │ │ ├── product-variants/ │ │ │ └── page.tsx │ │ ├── product-variants-advanced/ │ │ │ └── page.tsx │ │ ├── product-variants-multipage/ │ │ │ └── page.tsx │ │ ├── product-with-return-policy/ │ │ │ └── page.tsx │ │ ├── profile/ │ │ │ └── page.tsx │ │ ├── profile-advanced/ │ │ │ └── page.tsx │ │ ├── profile-organization/ │ │ │ └── page.tsx │ │ ├── quiz/ │ │ │ └── page.tsx │ │ ├── quiz-advanced/ │ │ │ └── page.tsx │ │ ├── quiz-biology/ │ │ │ └── page.tsx │ │ ├── recipe/ │ │ │ └── page.tsx │ │ ├── recipe-advanced/ │ │ │ └── page.tsx │ │ ├── restaurant/ │ │ │ └── page.tsx │ │ ├── review/ │ │ │ └── page.tsx │ │ ├── review-advanced/ │ │ │ └── page.tsx │ │ ├── review-movie/ │ │ │ └── page.tsx │ │ ├── social-media-posting/ │ │ │ └── page.tsx │ │ ├── software-app/ │ │ │ └── page.tsx │ │ ├── software-app-paid/ │ │ │ └── page.tsx │ │ ├── store-with-departments/ │ │ │ └── page.tsx │ │ ├── test-arrays/ │ │ │ └── page.tsx │ │ ├── test-nested/ │ │ │ └── page.tsx │ │ ├── test-url-params/ │ │ │ └── page.tsx │ │ ├── vacation-rental/ │ │ │ └── page.tsx │ │ ├── vacation-rental-advanced/ │ │ │ └── page.tsx │ │ ├── vacation-rental-apartment/ │ │ │ └── page.tsx │ │ ├── video/ │ │ │ └── page.tsx │ │ ├── video-advanced/ │ │ │ └── page.tsx │ │ ├── video-clips/ │ │ │ └── page.tsx │ │ ├── video-game/ │ │ │ └── page.tsx │ │ ├── video-live/ │ │ │ └── page.tsx │ │ ├── video-seekto/ │ │ │ └── page.tsx │ │ └── web-app/ │ │ └── page.tsx │ ├── components/ │ │ └── custom/ │ │ ├── PodcastSeriesJsonLd.tsx │ │ └── ServiceJsonLd.tsx │ ├── eslint.config.mjs │ ├── next.config.ts │ ├── package.json │ └── tsconfig.json ├── package.json ├── playwright.config.ts ├── pnpm-workspace.yaml ├── repomix.config.json ├── src/ │ ├── components/ │ │ ├── .gitkeep │ │ ├── AggregateRatingJsonLd.test.tsx │ │ ├── AggregateRatingJsonLd.tsx │ │ ├── ArticleJsonLd.test.tsx │ │ ├── ArticleJsonLd.tsx │ │ ├── BreadcrumbJsonLd.test.tsx │ │ ├── BreadcrumbJsonLd.tsx │ │ ├── CLAUDE.md │ │ ├── CarouselJsonLd.test.tsx │ │ ├── CarouselJsonLd.tsx │ │ ├── ClaimReviewJsonLd.test.tsx │ │ ├── ClaimReviewJsonLd.tsx │ │ ├── CourseJsonLd.test.tsx │ │ ├── CourseJsonLd.tsx │ │ ├── CreativeWorkJsonLd.test.tsx │ │ ├── CreativeWorkJsonLd.tsx │ │ ├── DatasetJsonLd.test.tsx │ │ ├── DatasetJsonLd.tsx │ │ ├── DiscussionForumPostingJsonLd.test.tsx │ │ ├── DiscussionForumPostingJsonLd.tsx │ │ ├── EmployerAggregateRatingJsonLd.test.tsx │ │ ├── EmployerAggregateRatingJsonLd.tsx │ │ ├── EventJsonLd.test.tsx │ │ ├── EventJsonLd.tsx │ │ ├── FAQJsonLd.test.tsx │ │ ├── FAQJsonLd.tsx │ │ ├── HowToJsonLd.test.tsx │ │ ├── HowToJsonLd.tsx │ │ ├── ImageJsonLd.test.tsx │ │ ├── ImageJsonLd.tsx │ │ ├── JobPostingJsonLd.test.tsx │ │ ├── JobPostingJsonLd.tsx │ │ ├── LocalBusinessJsonLd.test.tsx │ │ ├── LocalBusinessJsonLd.tsx │ │ ├── MerchantReturnPolicyJsonLd.test.tsx │ │ ├── MerchantReturnPolicyJsonLd.tsx │ │ ├── MovieCarouselJsonLd.test.tsx │ │ ├── MovieCarouselJsonLd.tsx │ │ ├── OrganizationJsonLd.test.tsx │ │ ├── OrganizationJsonLd.tsx │ │ ├── ProductJsonLd.test.tsx │ │ ├── ProductJsonLd.tsx │ │ ├── ProfilePageJsonLd.test.tsx │ │ ├── ProfilePageJsonLd.tsx │ │ ├── QuizJsonLd.test.tsx │ │ ├── QuizJsonLd.tsx │ │ ├── RecipeJsonLd.test.tsx │ │ ├── RecipeJsonLd.tsx │ │ ├── ReviewJsonLd.test.tsx │ │ ├── ReviewJsonLd.tsx │ │ ├── SoftwareApplicationJsonLd.test.tsx │ │ ├── SoftwareApplicationJsonLd.tsx │ │ ├── VacationRentalJsonLd.test.tsx │ │ ├── VacationRentalJsonLd.tsx │ │ ├── VideoJsonLd.test.tsx │ │ └── VideoJsonLd.tsx │ ├── core/ │ │ ├── JsonLdScript.test.tsx │ │ └── JsonLdScript.tsx │ ├── index.ts │ ├── pages/ │ │ ├── README.md │ │ ├── core/ │ │ │ ├── __snapshots__/ │ │ │ │ └── buildTags.test.tsx.snap │ │ │ ├── buildTags.test.tsx │ │ │ └── buildTags.tsx │ │ ├── index.ts │ │ ├── types/ │ │ │ └── index.ts │ │ └── utils/ │ │ └── processors.ts │ ├── types/ │ │ ├── article.types.ts │ │ ├── breadcrumb.types.ts │ │ ├── carousel.types.ts │ │ ├── claimreview.types.ts │ │ ├── common.types.ts │ │ ├── course.types.ts │ │ ├── creativework.types.ts │ │ ├── dataset.types.ts │ │ ├── discussionforum.types.ts │ │ ├── employer-aggregate-rating.types.ts │ │ ├── event.types.ts │ │ ├── faq.types.ts │ │ ├── howto.types.ts │ │ ├── image.types.ts │ │ ├── index.ts │ │ ├── jobposting.types.ts │ │ ├── localbusiness.types.ts │ │ ├── merchantreturnpolicy.types.ts │ │ ├── movie-carousel.types.ts │ │ ├── organization.types.ts │ │ ├── product.types.ts │ │ ├── profile.types.ts │ │ ├── quiz.types.ts │ │ ├── recipe.types.ts │ │ ├── review.types.ts │ │ ├── softwareApplication.types.ts │ │ ├── vacationrental.types.ts │ │ └── video.types.ts │ └── utils/ │ ├── processors.export.ts │ ├── processors.test.ts │ ├── processors.ts │ ├── stringify.test.ts │ └── stringify.ts ├── tests/ │ ├── e2e/ │ │ ├── .gitkeep │ │ ├── CLAUDE.md │ │ ├── aggregateRatingJsonLd.e2e.spec.ts │ │ ├── articleJsonLd.e2e.spec.ts │ │ ├── breadcrumbJsonLd.e2e.spec.ts │ │ ├── carouselJsonLd.e2e.spec.ts │ │ ├── claimReviewJsonLd.e2e.spec.ts │ │ ├── courseJsonLd.e2e.spec.ts │ │ ├── creativeWorkJsonLd.e2e.spec.ts │ │ ├── customComponents.e2e.spec.ts │ │ ├── datasetJsonLd.e2e.spec.ts │ │ ├── discussionForumPostingJsonLd.e2e.spec.ts │ │ ├── employerAggregateRatingJsonLd.e2e.spec.ts │ │ ├── eventJsonLd.e2e.spec.ts │ │ ├── faqJsonLd.e2e.spec.ts │ │ ├── howtoJsonLd.e2e.spec.ts │ │ ├── imageJsonLd.e2e.spec.ts │ │ ├── jobPostingJsonLd.e2e.spec.ts │ │ ├── jsonLdScript.e2e.spec.ts │ │ ├── jsonValidation.e2e.spec.ts │ │ ├── localBusinessJsonLd.e2e.spec.ts │ │ ├── merchantReturnPolicyJsonLd.e2e.spec.ts │ │ ├── movieCarouselJsonLd.e2e.spec.ts │ │ ├── organizationJsonLd.e2e.spec.ts │ │ ├── productJsonLd.e2e.spec.ts │ │ ├── profilePageJsonLd.e2e.spec.ts │ │ ├── quizJsonLd.e2e.spec.ts │ │ ├── recipeJsonLd.e2e.spec.ts │ │ ├── reviewJsonLd.e2e.spec.ts │ │ ├── security.e2e.spec.ts │ │ ├── softwareApplicationJsonLd.e2e.spec.ts │ │ ├── vacationRentalJsonLd.e2e.spec.ts │ │ └── videoJsonLd.e2e.spec.ts │ └── unit/ │ └── setup.ts ├── tsconfig.json ├── tsup.config.ts └── vitest.config.ts
SYMBOL INDEX (544 symbols across 172 files)
FILE: examples/app-router-showcase/app/aggregate-rating-restaurant/page.tsx
function Page (line 3) | function Page() {
FILE: examples/app-router-showcase/app/aggregate-rating/page.tsx
function Page (line 3) | function Page() {
FILE: examples/app-router-showcase/app/article/page.tsx
function ArticlePage (line 3) | function ArticlePage() {
FILE: examples/app-router-showcase/app/blog-posting/page.tsx
function BlogPostingPage (line 3) | function BlogPostingPage() {
FILE: examples/app-router-showcase/app/breadcrumb/advanced/page.tsx
function AdvancedBreadcrumbPage (line 3) | function AdvancedBreadcrumbPage() {
FILE: examples/app-router-showcase/app/breadcrumb/multiple/page.tsx
function MultipleBreadcrumbsPage (line 3) | function MultipleBreadcrumbsPage() {
FILE: examples/app-router-showcase/app/breadcrumb/page.tsx
function BreadcrumbPage (line 3) | function BreadcrumbPage() {
FILE: examples/app-router-showcase/app/carousel-course/page.tsx
function CarouselCoursePage (line 3) | function CarouselCoursePage() {
FILE: examples/app-router-showcase/app/carousel-movie/page.tsx
function CarouselMoviePage (line 3) | function CarouselMoviePage() {
FILE: examples/app-router-showcase/app/carousel-recipe/page.tsx
function CarouselRecipePage (line 3) | function CarouselRecipePage() {
FILE: examples/app-router-showcase/app/carousel-restaurant/page.tsx
function CarouselRestaurantPage (line 3) | function CarouselRestaurantPage() {
FILE: examples/app-router-showcase/app/carousel-summary/page.tsx
function CarouselSummaryPage (line 3) | function CarouselSummaryPage() {
FILE: examples/app-router-showcase/app/claim-review-advanced/page.tsx
function ClaimReviewAdvancedPage (line 3) | function ClaimReviewAdvancedPage() {
FILE: examples/app-router-showcase/app/claim-review-organization/page.tsx
function ClaimReviewOrganizationPage (line 3) | function ClaimReviewOrganizationPage() {
FILE: examples/app-router-showcase/app/claim-review/page.tsx
function ClaimReviewPage (line 3) | function ClaimReviewPage() {
FILE: examples/app-router-showcase/app/course-list-summary/page.tsx
function CourseListSummaryPage (line 3) | function CourseListSummaryPage() {
FILE: examples/app-router-showcase/app/course-list/page.tsx
type CourseProvider (line 3) | interface CourseProvider {
type Course (line 8) | interface Course {
function CourseListPage (line 15) | function CourseListPage() {
FILE: examples/app-router-showcase/app/course/page.tsx
function CoursePage (line 3) | function CoursePage() {
FILE: examples/app-router-showcase/app/creative-work-blog/page.tsx
function CreativeWorkBlogPage (line 3) | function CreativeWorkBlogPage() {
FILE: examples/app-router-showcase/app/creative-work-multiple/page.tsx
function CreativeWorkMultiplePage (line 3) | function CreativeWorkMultiplePage() {
FILE: examples/app-router-showcase/app/creative-work-news/page.tsx
function CreativeWorkNewsPage (line 3) | function CreativeWorkNewsPage() {
FILE: examples/app-router-showcase/app/creative-work/page.tsx
function CreativeWorkPage (line 3) | function CreativeWorkPage() {
FILE: examples/app-router-showcase/app/custom-podcast/page.tsx
function CustomPodcastPage (line 3) | function CustomPodcastPage() {
FILE: examples/app-router-showcase/app/custom-service/page.tsx
function CustomServicePage (line 3) | function CustomServicePage() {
FILE: examples/app-router-showcase/app/dataset-advanced/page.tsx
function DatasetAdvancedPage (line 3) | function DatasetAdvancedPage() {
FILE: examples/app-router-showcase/app/dataset-catalog/page.tsx
function DatasetCatalogPage (line 3) | function DatasetCatalogPage() {
FILE: examples/app-router-showcase/app/dataset-nested/page.tsx
function DatasetNestedPage (line 3) | function DatasetNestedPage() {
FILE: examples/app-router-showcase/app/dataset/page.tsx
function DatasetPage (line 3) | function DatasetPage() {
FILE: examples/app-router-showcase/app/discussion-forum-advanced/page.tsx
function DiscussionForumAdvancedPage (line 3) | function DiscussionForumAdvancedPage() {
FILE: examples/app-router-showcase/app/discussion-forum-deleted/page.tsx
function DiscussionForumDeletedPage (line 3) | function DiscussionForumDeletedPage() {
FILE: examples/app-router-showcase/app/discussion-forum/page.tsx
function DiscussionForumPage (line 3) | function DiscussionForumPage() {
FILE: examples/app-router-showcase/app/employer-aggregate-rating-advanced/page.tsx
function EmployerAggregateRatingAdvancedPage (line 3) | function EmployerAggregateRatingAdvancedPage() {
FILE: examples/app-router-showcase/app/employer-aggregate-rating-custom-scale/page.tsx
function EmployerAggregateRatingCustomScalePage (line 3) | function EmployerAggregateRatingCustomScalePage() {
FILE: examples/app-router-showcase/app/employer-aggregate-rating/page.tsx
function EmployerAggregateRatingPage (line 3) | function EmployerAggregateRatingPage() {
FILE: examples/app-router-showcase/app/event-cancelled/page.tsx
function EventCancelledPage (line 3) | function EventCancelledPage() {
FILE: examples/app-router-showcase/app/event-free/page.tsx
function EventFreePage (line 3) | function EventFreePage() {
FILE: examples/app-router-showcase/app/event-rescheduled/page.tsx
function EventRescheduledPage (line 3) | function EventRescheduledPage() {
FILE: examples/app-router-showcase/app/event/page.tsx
function EventPage (line 3) | function EventPage() {
FILE: examples/app-router-showcase/app/faq-advanced/page.tsx
function AdvancedFAQPage (line 3) | function AdvancedFAQPage() {
FILE: examples/app-router-showcase/app/faq-health/page.tsx
function HealthFAQPage (line 3) | function HealthFAQPage() {
FILE: examples/app-router-showcase/app/faq/page.tsx
function FAQPage (line 3) | function FAQPage() {
FILE: examples/app-router-showcase/app/howto-advanced/page.tsx
function HowToAdvancedPage (line 3) | function HowToAdvancedPage() {
FILE: examples/app-router-showcase/app/howto/page.tsx
function HowToPage (line 3) | function HowToPage() {
FILE: examples/app-router-showcase/app/image-advanced/page.tsx
function ImageAdvancedPage (line 3) | function ImageAdvancedPage() {
FILE: examples/app-router-showcase/app/image-multiple/page.tsx
function ImageMultiplePage (line 3) | function ImageMultiplePage() {
FILE: examples/app-router-showcase/app/image/page.tsx
function ImagePage (line 3) | function ImagePage() {
FILE: examples/app-router-showcase/app/job-posting-advanced/page.tsx
function AdvancedJobPostingPage (line 3) | function AdvancedJobPostingPage() {
FILE: examples/app-router-showcase/app/job-posting-remote/page.tsx
function RemoteJobPostingPage (line 3) | function RemoteJobPostingPage() {
FILE: examples/app-router-showcase/app/job-posting/page.tsx
function JobPostingPage (line 3) | function JobPostingPage() {
FILE: examples/app-router-showcase/app/jsonld-test-page/page.tsx
function JsonLdTestPage (line 15) | function JsonLdTestPage() {
FILE: examples/app-router-showcase/app/layout.tsx
function RootLayout (line 20) | function RootLayout({
FILE: examples/app-router-showcase/app/local-business/page.tsx
function LocalBusinessPage (line 3) | function LocalBusinessPage() {
FILE: examples/app-router-showcase/app/merchant-return-policy-advanced/page.tsx
function MerchantReturnPolicyAdvancedPage (line 3) | function MerchantReturnPolicyAdvancedPage() {
FILE: examples/app-router-showcase/app/merchant-return-policy-link/page.tsx
function MerchantReturnPolicyLinkPage (line 3) | function MerchantReturnPolicyLinkPage() {
FILE: examples/app-router-showcase/app/merchant-return-policy/page.tsx
function MerchantReturnPolicyPage (line 3) | function MerchantReturnPolicyPage() {
FILE: examples/app-router-showcase/app/mobile-app/page.tsx
function MobileAppPage (line 3) | function MobileAppPage() {
FILE: examples/app-router-showcase/app/movie-carousel-advanced/page.tsx
function MovieCarouselAdvancedPage (line 3) | function MovieCarouselAdvancedPage() {
FILE: examples/app-router-showcase/app/movie-carousel-summary/page.tsx
function MovieCarouselSummaryPage (line 3) | function MovieCarouselSummaryPage() {
FILE: examples/app-router-showcase/app/movie-carousel/page.tsx
function MovieCarouselPage (line 3) | function MovieCarouselPage() {
FILE: examples/app-router-showcase/app/news-article/page.tsx
function NewsArticlePage (line 3) | function NewsArticlePage() {
FILE: examples/app-router-showcase/app/online-store-loyalty/page.tsx
function OnlineStoreLoyaltyPage (line 3) | function OnlineStoreLoyaltyPage() {
FILE: examples/app-router-showcase/app/online-store/page.tsx
function OnlineStorePage (line 3) | function OnlineStorePage() {
FILE: examples/app-router-showcase/app/organization-advanced/page.tsx
function OrganizationAdvancedPage (line 3) | function OrganizationAdvancedPage() {
FILE: examples/app-router-showcase/app/organization-reviews/page.tsx
function OrganizationReviewsPage (line 3) | function OrganizationReviewsPage() {
FILE: examples/app-router-showcase/app/organization/page.tsx
function OrganizationPage (line 3) | function OrganizationPage() {
FILE: examples/app-router-showcase/app/page.tsx
function Home (line 4) | function Home() {
FILE: examples/app-router-showcase/app/product-3d-model/page.tsx
function Product3DModelPage (line 3) | function Product3DModelPage() {
FILE: examples/app-router-showcase/app/product-aggregate/page.tsx
function ProductAggregatePage (line 3) | function ProductAggregatePage() {
FILE: examples/app-router-showcase/app/product-certification/page.tsx
function ProductCertificationPage (line 3) | function ProductCertificationPage() {
FILE: examples/app-router-showcase/app/product-member-pricing/page.tsx
function ProductMemberPricingPage (line 3) | function ProductMemberPricingPage() {
FILE: examples/app-router-showcase/app/product-review/page.tsx
function ProductReviewPage (line 3) | function ProductReviewPage() {
FILE: examples/app-router-showcase/app/product-sale-pricing/page.tsx
function ProductSalePricingPage (line 3) | function ProductSalePricingPage() {
FILE: examples/app-router-showcase/app/product-shipping-options/page.tsx
function ProductShippingOptionsPage (line 3) | function ProductShippingOptionsPage() {
FILE: examples/app-router-showcase/app/product-unit-pricing/page.tsx
function ProductUnitPricingPage (line 3) | function ProductUnitPricingPage() {
FILE: examples/app-router-showcase/app/product-variants-advanced/page.tsx
function ProductVariantsAdvancedPage (line 3) | function ProductVariantsAdvancedPage() {
FILE: examples/app-router-showcase/app/product-variants-multipage/page.tsx
function ProductVariantsMultipagePage (line 3) | function ProductVariantsMultipagePage() {
FILE: examples/app-router-showcase/app/product-variants/page.tsx
function ProductVariantsPage (line 3) | function ProductVariantsPage() {
FILE: examples/app-router-showcase/app/product-with-return-policy/page.tsx
function ProductWithReturnPolicyPage (line 3) | function ProductWithReturnPolicyPage() {
FILE: examples/app-router-showcase/app/product/page.tsx
function ProductPage (line 3) | function ProductPage() {
FILE: examples/app-router-showcase/app/profile-advanced/page.tsx
function AdvancedProfilePage (line 3) | function AdvancedProfilePage() {
FILE: examples/app-router-showcase/app/profile-organization/page.tsx
function OrganizationProfilePage (line 3) | function OrganizationProfilePage() {
FILE: examples/app-router-showcase/app/profile/page.tsx
function ProfilePage (line 3) | function ProfilePage() {
FILE: examples/app-router-showcase/app/quiz-advanced/page.tsx
function AdvancedQuizPage (line 3) | function AdvancedQuizPage() {
FILE: examples/app-router-showcase/app/quiz-biology/page.tsx
function BiologyQuizPage (line 3) | function BiologyQuizPage() {
FILE: examples/app-router-showcase/app/quiz/page.tsx
function QuizPage (line 3) | function QuizPage() {
FILE: examples/app-router-showcase/app/recipe-advanced/page.tsx
function RecipeAdvancedPage (line 3) | function RecipeAdvancedPage() {
FILE: examples/app-router-showcase/app/recipe/page.tsx
function RecipePage (line 3) | function RecipePage() {
FILE: examples/app-router-showcase/app/restaurant/page.tsx
function RestaurantPage (line 3) | function RestaurantPage() {
FILE: examples/app-router-showcase/app/review-advanced/page.tsx
function Page (line 3) | function Page() {
FILE: examples/app-router-showcase/app/review-movie/page.tsx
function Page (line 3) | function Page() {
FILE: examples/app-router-showcase/app/review/page.tsx
function Page (line 3) | function Page() {
FILE: examples/app-router-showcase/app/social-media-posting/page.tsx
function SocialMediaPostingPage (line 3) | function SocialMediaPostingPage() {
FILE: examples/app-router-showcase/app/software-app-paid/page.tsx
function SoftwareAppPaidPage (line 3) | function SoftwareAppPaidPage() {
FILE: examples/app-router-showcase/app/software-app/page.tsx
function SoftwareAppPage (line 3) | function SoftwareAppPage() {
FILE: examples/app-router-showcase/app/store-with-departments/page.tsx
function StoreWithDepartmentsPage (line 3) | function StoreWithDepartmentsPage() {
FILE: examples/app-router-showcase/app/test-arrays/page.tsx
function TestArraysPage (line 3) | function TestArraysPage() {
FILE: examples/app-router-showcase/app/test-nested/page.tsx
function TestNestedPage (line 3) | function TestNestedPage() {
FILE: examples/app-router-showcase/app/test-url-params/page.tsx
function TestUrlParamsPage (line 3) | function TestUrlParamsPage() {
FILE: examples/app-router-showcase/app/vacation-rental-advanced/page.tsx
function VacationRentalAdvancedPage (line 3) | function VacationRentalAdvancedPage() {
FILE: examples/app-router-showcase/app/vacation-rental-apartment/page.tsx
function VacationRentalApartmentPage (line 3) | function VacationRentalApartmentPage() {
FILE: examples/app-router-showcase/app/vacation-rental/page.tsx
function VacationRentalPage (line 3) | function VacationRentalPage() {
FILE: examples/app-router-showcase/app/video-advanced/page.tsx
function VideoAdvancedPage (line 3) | function VideoAdvancedPage() {
FILE: examples/app-router-showcase/app/video-clips/page.tsx
function VideoClipsPage (line 3) | function VideoClipsPage() {
FILE: examples/app-router-showcase/app/video-game/page.tsx
function VideoGamePage (line 3) | function VideoGamePage() {
FILE: examples/app-router-showcase/app/video-live/page.tsx
function VideoLivePage (line 3) | function VideoLivePage() {
FILE: examples/app-router-showcase/app/video-seekto/page.tsx
function VideoSeekToPage (line 3) | function VideoSeekToPage() {
FILE: examples/app-router-showcase/app/video/page.tsx
function VideoPage (line 3) | function VideoPage() {
FILE: examples/app-router-showcase/app/web-app/page.tsx
function WebAppPage (line 3) | function WebAppPage() {
FILE: examples/app-router-showcase/components/custom/PodcastSeriesJsonLd.tsx
type PodcastEpisode (line 5) | interface PodcastEpisode {
type PodcastSeriesJsonLdProps (line 13) | interface PodcastSeriesJsonLdProps {
function PodcastSeriesJsonLd (line 26) | function PodcastSeriesJsonLd({
FILE: examples/app-router-showcase/components/custom/ServiceJsonLd.tsx
type ServiceJsonLdProps (line 5) | interface ServiceJsonLdProps {
function ServiceJsonLd (line 29) | function ServiceJsonLd({
FILE: src/components/AggregateRatingJsonLd.tsx
function AggregateRatingJsonLd (line 5) | function AggregateRatingJsonLd({
FILE: src/components/ArticleJsonLd.tsx
function ArticleJsonLd (line 10) | function ArticleJsonLd({
FILE: src/components/BreadcrumbJsonLd.tsx
function BreadcrumbJsonLd (line 5) | function BreadcrumbJsonLd(props: BreadcrumbJsonLdProps) {
FILE: src/components/CarouselJsonLd.tsx
function processSummaryItem (line 29) | function processSummaryItem(
function processCourseItem (line 47) | function processCourseItem(
function processMovieItem (line 66) | function processMovieItem(movie: MovieItem, index: number): CarouselList...
function processRecipeItem (line 89) | function processRecipeItem(
function processRestaurantItem (line 137) | function processRestaurantItem(
function CarouselJsonLd (line 193) | function CarouselJsonLd(props: CarouselJsonLdProps) {
FILE: src/components/ClaimReviewJsonLd.tsx
function ClaimReviewJsonLd (line 9) | function ClaimReviewJsonLd({
FILE: src/components/CourseJsonLd.tsx
function processSummaryItem (line 11) | function processSummaryItem(
function processCourseItem (line 29) | function processCourseItem(
function CourseJsonLd (line 48) | function CourseJsonLd(props: CourseJsonLdProps) {
FILE: src/components/CreativeWorkJsonLd.tsx
function CreativeWorkJsonLd (line 11) | function CreativeWorkJsonLd({
FILE: src/components/DatasetJsonLd.tsx
function DatasetJsonLd (line 13) | function DatasetJsonLd({
FILE: src/components/DiscussionForumPostingJsonLd.tsx
function DiscussionForumPostingJsonLd (line 13) | function DiscussionForumPostingJsonLd({
FILE: src/components/EmployerAggregateRatingJsonLd.tsx
function processEmployerItemReviewed (line 11) | function processEmployerItemReviewed(
function EmployerAggregateRatingJsonLd (line 63) | function EmployerAggregateRatingJsonLd({
FILE: src/components/EventJsonLd.tsx
function EventJsonLd (line 11) | function EventJsonLd({
FILE: src/components/FAQJsonLd.tsx
function processQuestion (line 10) | function processQuestion(input: QuestionInput): Question {
function FAQJsonLd (line 63) | function FAQJsonLd({
FILE: src/components/HowToJsonLd.tsx
function HowToJsonLd (line 13) | function HowToJsonLd({
FILE: src/components/ImageJsonLd.tsx
function ImageJsonLd (line 5) | function ImageJsonLd({
FILE: src/components/JobPostingJsonLd.tsx
function JobPostingJsonLd (line 13) | function JobPostingJsonLd({
FILE: src/components/LocalBusinessJsonLd.tsx
function processDepartment (line 12) | function processDepartment(
function LocalBusinessJsonLd (line 86) | function LocalBusinessJsonLd({
FILE: src/components/MerchantReturnPolicyJsonLd.tsx
function MerchantReturnPolicyJsonLd (line 9) | function MerchantReturnPolicyJsonLd({
FILE: src/components/MovieCarouselJsonLd.tsx
function processSummaryItem (line 16) | function processSummaryItem(
function processMovieItem (line 34) | function processMovieItem(
function MovieCarouselJsonLd (line 60) | function MovieCarouselJsonLd(props: MovieCarouselJsonLdProps) {
FILE: src/components/OrganizationJsonLd.tsx
function OrganizationJsonLd (line 14) | function OrganizationJsonLd(props: OrganizationJsonLdProps) {
FILE: src/components/ProductJsonLd.tsx
function ProductJsonLd (line 19) | function ProductJsonLd(props: ProductJsonLdProps) {
FILE: src/components/ProfilePageJsonLd.tsx
function ProfilePageJsonLd (line 5) | function ProfilePageJsonLd({
FILE: src/components/QuizJsonLd.tsx
function processQuestion (line 12) | function processQuestion(input: QuestionInput): Question {
function processAbout (line 70) | function processAbout(about: string | Thing): Record<string, unknown> {
function processEducationalAlignment (line 84) | function processEducationalAlignment(alignment: {
function QuizJsonLd (line 95) | function QuizJsonLd({
FILE: src/components/RecipeJsonLd.tsx
function RecipeJsonLd (line 12) | function RecipeJsonLd({
FILE: src/components/ReviewJsonLd.tsx
function ReviewJsonLd (line 10) | function ReviewJsonLd({
FILE: src/components/SoftwareApplicationJsonLd.tsx
function SoftwareApplicationJsonLd (line 18) | function SoftwareApplicationJsonLd({
FILE: src/components/VacationRentalJsonLd.tsx
function VacationRentalJsonLd (line 12) | function VacationRentalJsonLd({
FILE: src/components/VideoJsonLd.tsx
function VideoJsonLd (line 13) | function VideoJsonLd({
FILE: src/core/JsonLdScript.tsx
type JsonLdScriptProps (line 3) | interface JsonLdScriptProps<T = Record<string, unknown>> {
function JsonLdScript (line 9) | function JsonLdScript<T = Record<string, unknown>>({
FILE: src/pages/core/buildTags.tsx
function generateNextSeo (line 720) | function generateNextSeo(props: NextSeoProps): ReactNode[] {
function generateDefaultSeo (line 751) | function generateDefaultSeo(props: DefaultSeoProps): ReactNode[] {
FILE: src/pages/types/index.ts
type OpenGraphMedia (line 3) | interface OpenGraphMedia {
type OpenGraphVideoActors (line 12) | interface OpenGraphVideoActors {
type OpenGraph (line 17) | interface OpenGraph {
type OpenGraphProfile (line 36) | interface OpenGraphProfile {
type OpenGraphBook (line 43) | interface OpenGraphBook {
type OpenGraphArticle (line 50) | interface OpenGraphArticle {
type OpenGraphVideo (line 59) | interface OpenGraphVideo {
type Twitter (line 69) | interface Twitter {
type MobileAlternate (line 75) | interface MobileAlternate {
type LanguageAlternate (line 80) | interface LanguageAlternate {
type LinkTag (line 85) | interface LinkTag {
type BaseMetaTag (line 102) | interface BaseMetaTag {
type HTML5MetaTag (line 107) | interface HTML5MetaTag extends BaseMetaTag {
type RDFaMetaTag (line 113) | interface RDFaMetaTag extends BaseMetaTag {
type HTTPEquivMetaTag (line 119) | interface HTTPEquivMetaTag extends BaseMetaTag {
type MetaTag (line 130) | type MetaTag = HTML5MetaTag | RDFaMetaTag | HTTPEquivMetaTag;
type ImagePrevSize (line 132) | type ImagePrevSize = "none" | "standard" | "large";
type AdditionalRobotsProps (line 134) | interface AdditionalRobotsProps {
type NextSeoProps (line 145) | interface NextSeoProps {
type DefaultSeoProps (line 164) | interface DefaultSeoProps extends NextSeoProps {
type BuildTagsParams (line 174) | interface BuildTagsParams extends DefaultSeoProps, NextSeoProps {}
FILE: src/pages/utils/processors.ts
function processOpenGraphMedia (line 7) | function processOpenGraphMedia(
function buildRobotsContent (line 70) | function buildRobotsContent(
function processTitle (line 116) | function processTitle(
function generateTagKey (line 135) | function generateTagKey(tag: {
FILE: src/types/article.types.ts
type Publisher (line 3) | type Publisher =
type ArticleBase (line 10) | interface ArticleBase {
type Article (line 35) | interface Article extends ArticleBase {
type NewsArticle (line 39) | interface NewsArticle extends ArticleBase {
type BlogPosting (line 43) | interface BlogPosting extends ArticleBase {
type Blog (line 47) | interface Blog extends ArticleBase {
type ArticleJsonLdProps (line 51) | type ArticleJsonLdProps = (
FILE: src/types/breadcrumb.types.ts
type BreadcrumbListItem (line 4) | interface BreadcrumbListItem {
type ListItem (line 10) | interface ListItem {
type BreadcrumbList (line 18) | interface BreadcrumbList {
type BreadcrumbJsonLdProps (line 25) | type BreadcrumbJsonLdProps =
FILE: src/types/carousel.types.ts
type CarouselContentType (line 17) | type CarouselContentType = "Course" | "Movie" | "Recipe" | "Restaurant";
type CarouselListItem (line 20) | interface CarouselListItem {
type CarouselItemList (line 28) | interface CarouselItemList {
type SummaryPageItem (line 35) | type SummaryPageItem = string | { url: string; position?: number };
type CourseItem (line 38) | type CourseItem = Omit<Course, "@type" | "provider"> & {
type MovieItem (line 42) | type MovieItem = Omit<Movie, "@type" | "image"> & {
type RecipeItem (line 53) | type RecipeItem = Omit<
type RestaurantItem (line 66) | type RestaurantItem = Omit<
type CarouselJsonLdBaseProps (line 91) | interface CarouselJsonLdBaseProps {
type SummaryPageProps (line 97) | interface SummaryPageProps extends CarouselJsonLdBaseProps {
type CourseCarouselProps (line 102) | interface CourseCarouselProps extends CarouselJsonLdBaseProps {
type MovieCarouselProps (line 107) | interface MovieCarouselProps extends CarouselJsonLdBaseProps {
type RecipeCarouselProps (line 112) | interface RecipeCarouselProps extends CarouselJsonLdBaseProps {
type RestaurantCarouselProps (line 117) | interface RestaurantCarouselProps extends CarouselJsonLdBaseProps {
type CarouselJsonLdProps (line 123) | type CarouselJsonLdProps =
FILE: src/types/claimreview.types.ts
type ClaimReviewRating (line 4) | interface ClaimReviewRating extends Rating {
type ClaimCreativeWork (line 10) | interface ClaimCreativeWork {
type Claim (line 22) | interface Claim {
type ClaimReview (line 38) | interface ClaimReview {
type ClaimReviewJsonLdProps (line 48) | type ClaimReviewJsonLdProps = Omit<ClaimReview, "@type"> & {
FILE: src/types/common.types.ts
type Thing (line 4) | interface Thing {
type ImageObject (line 11) | interface ImageObject {
type Person (line 19) | interface Person extends Thing {
type Organization (line 37) | interface Organization extends Thing {
type PostalAddress (line 80) | interface PostalAddress {
type Place (line 89) | interface Place {
type ContactPoint (line 95) | interface ContactPoint {
type QuantitativeValue (line 102) | interface QuantitativeValue {
type SimpleMonetaryAmount (line 112) | interface SimpleMonetaryAmount {
type MerchantReturnPolicySeasonalOverride (line 118) | interface MerchantReturnPolicySeasonalOverride {
type MerchantReturnPolicy (line 126) | interface MerchantReturnPolicy {
type PriceTypeEnumeration (line 167) | type PriceTypeEnumeration =
type CreditCard (line 173) | interface CreditCard {
type UnitPriceSpecification (line 178) | interface UnitPriceSpecification {
type TierRequirement (line 194) | type TierRequirement =
type TierBenefit (line 203) | type TierBenefit =
type MemberProgramTier (line 209) | interface MemberProgramTier {
type MemberProgram (line 222) | interface MemberProgram {
type Author (line 233) | type Author =
type GeoCoordinates (line 240) | interface GeoCoordinates {
type Rating (line 246) | interface Rating {
type Review (line 253) | interface Review {
type AggregateRating (line 261) | interface AggregateRating {
type OpeningHoursSpecification (line 270) | interface OpeningHoursSpecification {
type VideoObject (line 279) | interface VideoObject {
type InteractionCounter (line 291) | interface InteractionCounter {
type Brand (line 297) | interface Brand {
type BedDetails (line 302) | interface BedDetails {
type LocationFeatureSpecification (line 308) | interface LocationFeatureSpecification {
type Accommodation (line 314) | interface Accommodation {
type Certification (line 337) | interface Certification {
type PeopleAudience (line 349) | interface PeopleAudience {
type SizeSpecification (line 362) | interface SizeSpecification {
type ThreeDModel (line 420) | interface ThreeDModel {
type DefinedRegion (line 428) | interface DefinedRegion {
type ShippingDeliveryTime (line 435) | interface ShippingDeliveryTime {
type OfferShippingDetails (line 441) | interface OfferShippingDetails {
FILE: src/types/course.types.ts
type Provider (line 4) | type Provider = string | Organization | Omit<Organization, "@type">;
type Course (line 7) | interface Course {
type CourseListItem (line 16) | type CourseListItem = Omit<Course, "@type" | "provider"> & {
type CourseListItemSchema (line 21) | interface CourseListItemSchema {
type CourseItemList (line 29) | interface CourseItemList {
type SummaryPageItem (line 36) | type SummaryPageItem = string | { url: string; position?: number };
type CourseJsonLdBaseProps (line 39) | interface CourseJsonLdBaseProps {
type SingleCourseProps (line 45) | interface SingleCourseProps extends CourseJsonLdBaseProps {
type CourseListSummaryProps (line 54) | interface CourseListSummaryProps extends CourseJsonLdBaseProps {
type CourseListAllInOneProps (line 60) | interface CourseListAllInOneProps extends CourseJsonLdBaseProps {
type CourseJsonLdProps (line 66) | type CourseJsonLdProps =
FILE: src/types/creativework.types.ts
type WebPageElement (line 4) | interface WebPageElement {
type Publisher (line 11) | type Publisher =
type CreativeWorkBase (line 19) | interface CreativeWorkBase {
type CreativeWork (line 50) | interface CreativeWork extends CreativeWorkBase {
type Article (line 54) | interface Article extends CreativeWorkBase {
type NewsArticle (line 59) | interface NewsArticle extends CreativeWorkBase {
type Blog (line 64) | interface Blog extends CreativeWorkBase {
type BlogPosting (line 68) | interface BlogPosting extends CreativeWorkBase {
type Comment (line 73) | interface Comment extends CreativeWorkBase {
type Course (line 78) | interface Course extends CreativeWorkBase {
type HowTo (line 84) | interface HowTo extends CreativeWorkBase {
type Message (line 89) | interface Message extends CreativeWorkBase {
type Review (line 93) | interface Review extends CreativeWorkBase {
type WebPage (line 104) | interface WebPage extends CreativeWorkBase {
type CreativeWorkJsonLdProps (line 109) | type CreativeWorkJsonLdProps = {
FILE: src/types/dataset.types.ts
type GeoShape (line 5) | interface GeoShape {
type PropertyValue (line 13) | interface PropertyValue {
type CreativeWork (line 20) | interface CreativeWork {
type DatasetPlace (line 28) | interface DatasetPlace {
type DataDownload (line 39) | interface DataDownload {
type DataCatalog (line 49) | interface DataCatalog {
type Dataset (line 58) | interface Dataset {
type DatasetJsonLdProps (line 99) | type DatasetJsonLdProps = Omit<Dataset, "@type"> & {
FILE: src/types/discussionforum.types.ts
type WebPage (line 9) | interface WebPage {
type CreativeWork (line 17) | interface CreativeWork {
type SharedContent (line 24) | type SharedContent =
type Comment (line 34) | interface Comment {
type DiscussionForumPostingBase (line 53) | interface DiscussionForumPostingBase {
type DiscussionForumPosting (line 77) | interface DiscussionForumPosting extends DiscussionForumPostingBase {
type SocialMediaPosting (line 81) | interface SocialMediaPosting extends DiscussionForumPostingBase {
type DiscussionForumPostingJsonLdProps (line 86) | type DiscussionForumPostingJsonLdProps = (
FILE: src/types/employer-aggregate-rating.types.ts
type EmployerAggregateRating (line 4) | interface EmployerAggregateRating {
type EmployerAggregateRatingJsonLdProps (line 15) | type EmployerAggregateRatingJsonLdProps = Omit<
FILE: src/types/event.types.ts
type Place (line 10) | interface Place {
type Offer (line 17) | interface Offer {
type PerformingGroup (line 27) | interface PerformingGroup extends Thing {
type Performer (line 32) | type Performer =
type Organizer (line 40) | type Organizer =
type EventStatusType (line 48) | type EventStatusType =
type EventBase (line 55) | interface EventBase {
type Event (line 75) | interface Event extends EventBase {
type EventJsonLdProps (line 80) | type EventJsonLdProps = Omit<Event, "@type"> & {
FILE: src/types/faq.types.ts
type Answer (line 4) | interface Answer {
type Question (line 10) | interface Question {
type FAQPage (line 17) | interface FAQPage {
type QuestionInput (line 24) | type QuestionInput =
type FAQJsonLdProps (line 37) | interface FAQJsonLdProps {
FILE: src/types/howto.types.ts
type HowToSupply (line 12) | interface HowToSupply {
type HowToTool (line 31) | interface HowToTool {
type HowToDirection (line 46) | interface HowToDirection {
type HowToTip (line 59) | interface HowToTip {
type HowToStep (line 69) | interface HowToStep {
type HowToSection (line 88) | interface HowToSection {
type Step (line 98) | type Step =
type Supply (line 108) | type Supply = string | HowToSupply | Omit<HowToSupply, "@type">;
type Tool (line 113) | type Tool = string | HowToTool | Omit<HowToTool, "@type">;
type EstimatedCost (line 118) | type EstimatedCost =
type HowToYield (line 126) | type HowToYield =
type HowTo (line 135) | interface HowTo {
type HowToJsonLdProps (line 154) | type HowToJsonLdProps = Omit<HowTo, "@type"> & {
FILE: src/types/image.types.ts
type ImageBase (line 4) | interface ImageBase {
type ImageMetadata (line 14) | interface ImageMetadata extends ImageBase {
type ImageJsonLdProps (line 19) | type ImageJsonLdProps = {
FILE: src/types/jobposting.types.ts
type Country (line 8) | interface Country {
type State (line 13) | interface State {
type AdministrativeArea (line 18) | type AdministrativeArea = Country | State;
type Place (line 21) | interface Place {
type PropertyValue (line 27) | interface PropertyValue {
type MonetaryAmount (line 34) | interface MonetaryAmount {
type EducationalOccupationalCredential (line 41) | interface EducationalOccupationalCredential {
type OccupationalExperienceRequirements (line 47) | interface OccupationalExperienceRequirements {
type EmploymentType (line 53) | type EmploymentType =
type JobLocationType (line 64) | type JobLocationType = "TELECOMMUTE";
type JobPostingBase (line 67) | interface JobPostingBase {
type JobPosting (line 107) | interface JobPosting extends JobPostingBase {
type JobPostingJsonLdProps (line 112) | type JobPostingJsonLdProps = Omit<JobPostingBase, "@type"> & {
FILE: src/types/localbusiness.types.ts
type LocalBusinessBase (line 11) | interface LocalBusinessBase {
type LocalBusiness (line 51) | interface LocalBusiness extends LocalBusinessBase {
type Restaurant (line 55) | interface Restaurant extends LocalBusinessBase {
type Store (line 61) | interface Store extends LocalBusinessBase {
type Pharmacy (line 65) | interface Pharmacy extends LocalBusinessBase {
type DaySpa (line 69) | interface DaySpa extends LocalBusinessBase {
type HealthClub (line 73) | interface HealthClub extends LocalBusinessBase {
type EntertainmentBusiness (line 77) | interface EntertainmentBusiness extends LocalBusinessBase {
type Electrician (line 81) | interface Electrician extends LocalBusinessBase {
type Plumber (line 85) | interface Plumber extends LocalBusinessBase {
type Locksmith (line 89) | interface Locksmith extends LocalBusinessBase {
type LocalBusinessJsonLdProps (line 93) | type LocalBusinessJsonLdProps = Omit<LocalBusinessBase, "department"> & {
FILE: src/types/merchantreturnpolicy.types.ts
type MerchantReturnPolicyJsonLdProps (line 4) | type MerchantReturnPolicyJsonLdProps = Omit<
FILE: src/types/movie-carousel.types.ts
type Director (line 9) | type Director = string | Person | Omit<Person, "@type">;
type Movie (line 12) | interface Movie {
type MovieListItem (line 24) | type MovieListItem = Omit<Movie, "@type" | "image"> & {
type MovieCarouselListItem (line 33) | interface MovieCarouselListItem {
type MovieCarouselItemList (line 41) | interface MovieCarouselItemList {
type SummaryPageItem (line 48) | type SummaryPageItem = string | { url: string; position?: number };
type MovieCarouselJsonLdProps (line 51) | type MovieCarouselJsonLdProps = {
FILE: src/types/organization.types.ts
type OrganizationBase (line 9) | type OrganizationBase = Omit<Organization, "@type">;
type OnlineStore (line 11) | interface OnlineStore extends OrganizationBase {
type OrganizationJsonLdProps (line 25) | type OrganizationJsonLdProps = (
FILE: src/types/product.types.ts
type ItemAvailability (line 18) | type ItemAvailability =
type PriceSpecification (line 41) | interface PriceSpecification {
type ProductOffer (line 50) | interface ProductOffer {
type AggregateOffer (line 97) | interface AggregateOffer {
type ProductListItem (line 107) | interface ProductListItem {
type ProductItemList (line 114) | interface ProductItemList {
type ProductReview (line 120) | interface ProductReview extends Omit<Review, "@type"> {
type ProductBase (line 128) | interface ProductBase {
type Product (line 225) | interface Product extends ProductBase {
type VariesBy (line 230) | type VariesBy =
type ProductGroup (line 245) | interface ProductGroup {
type ProductJsonLdProps (line 291) | type ProductJsonLdProps = (
FILE: src/types/profile.types.ts
type ProfilePage (line 4) | interface ProfilePage {
type ProfilePageJsonLdProps (line 14) | type ProfilePageJsonLdProps = {
FILE: src/types/quiz.types.ts
type Answer (line 6) | interface Answer {
type Question (line 12) | interface Question {
type AlignmentObject (line 21) | interface AlignmentObject {
type Quiz (line 28) | interface Quiz {
type QuestionInput (line 37) | type QuestionInput =
type QuizJsonLdProps (line 50) | interface QuizJsonLdProps {
FILE: src/types/recipe.types.ts
type NutritionInformation (line 8) | interface NutritionInformation {
type HowToStep (line 24) | interface HowToStep {
type HowToSection (line 32) | interface HowToSection {
type Instruction (line 38) | type Instruction =
type RecipeImage (line 44) | type RecipeImage =
type Recipe (line 50) | interface Recipe {
type RecipeJsonLdProps (line 74) | type RecipeJsonLdProps = Omit<Recipe, "@type"> & {
FILE: src/types/review.types.ts
type ItemReviewedType (line 3) | type ItemReviewedType =
type ItemReviewedInput (line 22) | type ItemReviewedInput =
type ReviewJsonLdProps (line 26) | interface ReviewJsonLdProps {
type AggregateRatingJsonLdProps (line 56) | interface AggregateRatingJsonLdProps {
FILE: src/types/softwareApplication.types.ts
type SoftwareApplicationBase (line 11) | interface SoftwareApplicationBase {
type SoftwareApplication (line 52) | interface SoftwareApplication extends SoftwareApplicationBase {
type MobileApplication (line 56) | interface MobileApplication extends SoftwareApplicationBase {
type WebApplication (line 60) | interface WebApplication extends SoftwareApplicationBase {
type VideoGame (line 64) | interface VideoGame extends SoftwareApplicationBase {
type GameApplication (line 69) | interface GameApplication extends SoftwareApplicationBase {
type SocialNetworkingApplication (line 73) | interface SocialNetworkingApplication extends SoftwareApplicationBase {
type TravelApplication (line 77) | interface TravelApplication extends SoftwareApplicationBase {
type ShoppingApplication (line 81) | interface ShoppingApplication extends SoftwareApplicationBase {
type SportsApplication (line 85) | interface SportsApplication extends SoftwareApplicationBase {
type LifestyleApplication (line 89) | interface LifestyleApplication extends SoftwareApplicationBase {
type BusinessApplication (line 93) | interface BusinessApplication extends SoftwareApplicationBase {
type DesignApplication (line 97) | interface DesignApplication extends SoftwareApplicationBase {
type DeveloperApplication (line 101) | interface DeveloperApplication extends SoftwareApplicationBase {
type DriverApplication (line 105) | interface DriverApplication extends SoftwareApplicationBase {
type EducationalApplication (line 109) | interface EducationalApplication extends SoftwareApplicationBase {
type HealthApplication (line 113) | interface HealthApplication extends SoftwareApplicationBase {
type FinanceApplication (line 117) | interface FinanceApplication extends SoftwareApplicationBase {
type SecurityApplication (line 121) | interface SecurityApplication extends SoftwareApplicationBase {
type BrowserApplication (line 125) | interface BrowserApplication extends SoftwareApplicationBase {
type CommunicationApplication (line 129) | interface CommunicationApplication extends SoftwareApplicationBase {
type DesktopEnhancementApplication (line 133) | interface DesktopEnhancementApplication extends SoftwareApplicationBase {
type EntertainmentApplication (line 137) | interface EntertainmentApplication extends SoftwareApplicationBase {
type MultimediaApplication (line 141) | interface MultimediaApplication extends SoftwareApplicationBase {
type HomeApplication (line 145) | interface HomeApplication extends SoftwareApplicationBase {
type UtilitiesApplication (line 149) | interface UtilitiesApplication extends SoftwareApplicationBase {
type ReferenceApplication (line 153) | interface ReferenceApplication extends SoftwareApplicationBase {
type VideoGameCoTyped (line 158) | type VideoGameCoTyped =
type ApplicationType (line 164) | type ApplicationType =
type SoftwareApplicationJsonLdProps (line 192) | type SoftwareApplicationJsonLdProps = (
FILE: src/types/vacationrental.types.ts
type VacationRentalBase (line 10) | interface VacationRentalBase {
type VacationRental (line 42) | interface VacationRental extends VacationRentalBase {
type VacationRentalJsonLdProps (line 46) | type VacationRentalJsonLdProps = Omit<VacationRentalBase, "@type"> & {
FILE: src/types/video.types.ts
type Duration (line 9) | type Duration = string;
type Thumbnail (line 12) | type Thumbnail = string | ImageObject | Omit<ImageObject, "@type">;
type Region (line 15) | type Region = string | string[];
type BroadcastEvent (line 18) | interface BroadcastEvent {
type Clip (line 27) | interface Clip {
type SeekToAction (line 36) | interface SeekToAction {
type PotentialAction (line 43) | interface PotentialAction {
type VideoObjectBase (line 50) | interface VideoObjectBase {
type VideoObject (line 76) | interface VideoObject extends VideoObjectBase {
type VideoJsonLdProps (line 81) | type VideoJsonLdProps = Omit<VideoObject, "@type"> & {
FILE: src/utils/processors.ts
constant SCHEMA_TYPES (line 112) | const SCHEMA_TYPES = {
function hasType (line 177) | function hasType<T extends { "@type": string }>(obj: unknown): obj is T {
function isString (line 181) | function isString(value: unknown): value is string {
function processSchemaType (line 186) | function processSchemaType<T extends { "@type": string }>(
function processOrganizationFields (line 214) | function processOrganizationFields(org: Organization): void {
function processAuthor (line 246) | function processAuthor(author: Author): Person | Organization {
function processImage (line 324) | function processImage(
function processAddress (line 341) | function processAddress(
function processContactPoint (line 357) | function processContactPoint(
function processLogo (line 371) | function processLogo(
function processNumberOfEmployees (line 382) | function processNumberOfEmployees(
function processGeo (line 401) | function processGeo(
function processOpeningHours (line 412) | function processOpeningHours(
function processReview (line 426) | function processReview(review: Review | Omit<Review, "@type">): Review {
function processBreadcrumbItem (line 454) | function processBreadcrumbItem(
function processPlace (line 471) | function processPlace(
function processPerformer (line 493) | function processPerformer(
function processOrganizer (line 526) | function processOrganizer(organizer: Organizer): Person | Organization {
function processOrganization (line 554) | function processOrganization(
function processOffer (line 580) | function processOffer(offer: Offer | Omit<Offer, "@type">): Offer {
function processPublisher (line 589) | function processPublisher(
function processNutrition (line 631) | function processNutrition(
function processAggregateRating (line 645) | function processAggregateRating(
type WebPage (line 654) | type WebPage = {
function processMainEntityOfPage (line 664) | function processMainEntityOfPage(
function processSimpleMonetaryAmount (line 679) | function processSimpleMonetaryAmount(
function processReturnPolicySeasonalOverride (line 708) | function processReturnPolicySeasonalOverride(
function processMerchantReturnPolicy (line 725) | function processMerchantReturnPolicy(
function processTierRequirement (line 812) | function processTierRequirement(
function processTierBenefit (line 867) | function processTierBenefit(
function processMembershipPointsEarned (line 897) | function processMembershipPointsEarned(
function processMemberProgramTier (line 917) | function processMemberProgramTier(
function processMemberProgram (line 952) | function processMemberProgram(
function processVideo (line 977) | function processVideo(
function processBroadcastEvent (line 988) | function processBroadcastEvent(
function processClip (line 1008) | function processClip(clip: Clip | Omit<Clip, "@type">): Clip {
function processSeekToAction (line 1026) | function processSeekToAction(
function processInstruction (line 1046) | function processInstruction(
function processDirector (line 1096) | function processDirector(director: Director): Person {
function processCreator (line 1112) | function processCreator(
function processIdentifier (line 1125) | function processIdentifier(
function processSpatialCoverage (line 1143) | function processSpatialCoverage(
function processDataDownload (line 1183) | function processDataDownload(
function processLicense (line 1194) | function processLicense(
function processDataCatalog (line 1209) | function processDataCatalog(
function processHiringOrganization (line 1222) | function processHiringOrganization(
function processJobLocation (line 1250) | function processJobLocation(
function processMonetaryAmount (line 1278) | function processMonetaryAmount(
function processRating (line 1297) | function processRating(rating: Rating | Omit<Rating, "@type">): Rating {
function processJobPropertyValue (line 1306) | function processJobPropertyValue(
function processApplicantLocationRequirements (line 1322) | function processApplicantLocationRequirements(
function processEducationRequirements (line 1351) | function processEducationRequirements(
function processExperienceRequirements (line 1372) | function processExperienceRequirements(
function processInteractionStatistic (line 1395) | function processInteractionStatistic(
function processSharedContent (line 1409) | function processSharedContent(
function processComment (line 1446) | function processComment(
function processIsPartOf (line 1490) | function processIsPartOf(
function processBrand (line 1510) | function processBrand(
function processBedDetails (line 1541) | function processBedDetails(
function processLocationFeatureSpecification (line 1552) | function processLocationFeatureSpecification(
function processAccommodation (line 1568) | function processAccommodation(
function processProvider (line 1614) | function processProvider(provider: Provider): Organization {
function processFunder (line 1628) | function processFunder(
function processFunderSingle (line 1642) | function processFunderSingle(funder: Author): Person | Organization {
function processScreenshot (line 1669) | function processScreenshot(
function processFeatureList (line 1680) | function processFeatureList(
function processClaimReviewRating (line 1693) | function processClaimReviewRating(
function processClaim (line 1704) | function processClaim(claim: Claim | Omit<Claim, "@type">): Claim {
function processAppearance (line 1734) | function processAppearance(
function processWebPageElement (line 1766) | function processWebPageElement(
function processProductOffer (line 1782) | function processProductOffer(
function processAggregateOffer (line 1842) | function processAggregateOffer(
function processPriceSpecification (line 1863) | function processPriceSpecification(
function processUnitPriceSpecification (line 1892) | function processUnitPriceSpecification(
function processQuantitativeValue (line 1928) | function processQuantitativeValue(
function processProductItemList (line 1949) | function processProductItemList(
function processProductReview (line 1980) | function processProductReview(
function processVariesBy (line 2019) | function processVariesBy(
function processProductVariant (line 2042) | function processProductVariant(
function processCertification (line 2166) | function processCertification(
function processPeopleAudience (line 2199) | function processPeopleAudience(
function processSizeSpecification (line 2220) | function processSizeSpecification(
function processThreeDModel (line 2237) | function processThreeDModel(
function processDefinedRegion (line 2261) | function processDefinedRegion(
function processShippingDeliveryTime (line 2272) | function processShippingDeliveryTime(
function processOfferShippingDetails (line 2298) | function processOfferShippingDetails(
type ItemReviewedType (line 2333) | type ItemReviewedType =
type ItemReviewedInput (line 2352) | type ItemReviewedInput =
function processItemReviewed (line 2361) | function processItemReviewed(
function processHowToDirection (line 2405) | function processHowToDirection(
function processHowToTip (line 2432) | function processHowToTip(
function processHowToStepItem (line 2443) | function processHowToStepItem(
function processHowToStep (line 2472) | function processHowToStep(
function processHowToSection (line 2502) | function processHowToSection(
function processStep (line 2528) | function processStep(
function processHowToSupply (line 2563) | function processHowToSupply(supply: Supply): HowToSupply {
function processHowToTool (line 2601) | function processHowToTool(tool: Tool): HowToTool {
function processEstimatedCost (line 2634) | function processEstimatedCost(
function processHowToYield (line 2649) | function processHowToYield(
FILE: src/utils/stringify.ts
type JsonValueScalar (line 6) | type JsonValueScalar = string | boolean | number;
type JsonValue (line 7) | type JsonValue =
type JsonReplacer (line 11) | type JsonReplacer = (_: string, value: JsonValue) => JsonValue | undefined;
function isNever (line 45) | function isNever(_: never): void {}
FILE: tests/e2e/jsonLdScript.e2e.spec.ts
constant PAGE_URL (line 4) | const PAGE_URL = "/jsonld-test-page";
constant SCRIPT_SELECTOR (line 5) | const SCRIPT_SELECTOR = 'script[type="application/ld+json"]#e2e-jsonld-s...
FILE: tsup.config.ts
method esbuildOptions (line 15) | esbuildOptions(options) {
Condensed preview — 286 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,963K chars).
[
{
"path": ".changeset/README.md",
"chars": 510,
"preview": "# Changesets\n\nHello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that wo"
},
{
"path": ".changeset/config.json",
"chars": 292,
"preview": "{\n \"$schema\": \"https://unpkg.com/@changesets/config@3.1.1/schema.json\",\n \"changelog\": \"@changesets/cli/changelog\",\n \""
},
{
"path": ".editorconfig",
"chars": 188,
"preview": "root = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ni"
},
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 475,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"\"\nlabels: \"\"\nassignees: \"\"\n---\n\n**Describe the bu"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 698,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"\"\nlabels: \"\"\nassignees: \"\"\n---\n\n**Is your feat"
},
{
"path": ".github/dependabot.yml",
"chars": 187,
"preview": "version: 2\nupdates:\n - package-ecosystem: \"npm\"\n directory: \"/\"\n schedule:\n interval: \"weekly\"\n commit-me"
},
{
"path": ".github/pull_request_template.md",
"chars": 0,
"preview": ""
},
{
"path": ".github/workflows/changeset-check.yml",
"chars": 3996,
"preview": "name: Changeset Check\n\non:\n pull_request:\n types: [opened, synchronize]\n\njobs:\n changeset-check:\n name: Check fo"
},
{
"path": ".github/workflows/changesets.yml",
"chars": 6143,
"preview": "name: Changesets\n\non:\n push:\n branches:\n - main\n\nconcurrency: ${{ github.workflow }}-${{ github.ref }}\n\npermiss"
},
{
"path": ".github/workflows/ci.yml",
"chars": 5516,
"preview": "name: CI\n\non:\n pull_request:\n branches: [main]\n workflow_dispatch:\n\nconcurrency:\n group: ${{ github.workflow }}-${"
},
{
"path": ".github/workflows/release.yml",
"chars": 3270,
"preview": "name: Manual Release\n\non:\n workflow_dispatch:\n inputs:\n version:\n description: \"Version to release (e.g."
},
{
"path": ".gitignore",
"chars": 668,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# Dependencies\nnode_modules\n/.pnp"
},
{
"path": ".husky/pre-commit",
"chars": 16,
"preview": "pnpm lint-staged"
},
{
"path": ".npmignore",
"chars": 448,
"preview": "# Source code\nsrc/\nexamples/\n\n# Config files\n.husky/\n.github/\n.vscode/\n.editorconfig\n.prettierignore\ncommitlint.config.j"
},
{
"path": ".prettierignore",
"chars": 185,
"preview": "node_modules\n.next\n.swc\ncoverage\ndist\nbuild\npackage.json\npnpm-lock.yaml\n\n# Ignore example project built files if any\nexa"
},
{
"path": ".vscode/settings.json",
"chars": 98,
"preview": "{\n \"cSpell.words\": [\"noindex\", \"nofollow\"],\n \"typescript.tsdk\": \"node_modules/typescript/lib\"\n}\n"
},
{
"path": "ADDING_NEW_COMPONENTS.md",
"chars": 18585,
"preview": "# Adding New Components to Next SEO\n\nThis guide walks through the process of adding new JSON-LD structured data componen"
},
{
"path": "AGENTS.md",
"chars": 4158,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "CHANGELOG.md",
"chars": 864,
"preview": "# next-seo\n\n## 7.2.0\n\n### Minor Changes\n\n- 28c684e: Add `review` and `aggregateRating` props to OrganizationJsonLd compo"
},
{
"path": "CLAUDE.md",
"chars": 4158,
"preview": "# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## "
},
{
"path": "CONTRIBUTING.md",
"chars": 4839,
"preview": "# Contributing to Next SEO\n\nThank you for your interest in contributing to Next SEO! We are open to all and any contribu"
},
{
"path": "CUSTOM_COMPONENTS.md",
"chars": 18970,
"preview": "# Creating Custom JSON-LD Components with Next SEO\n\nThis guide shows you how to create your own structured data componen"
},
{
"path": "LICENSE",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2018 Gary Meehan\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "LICENSE.md",
"chars": 1068,
"preview": "MIT License\n\nCopyright (c) 2018 Gary Meehan\n\nPermission is hereby granted, free of charge, to any person obtaining a cop"
},
{
"path": "LIST.md",
"chars": 1262,
"preview": "# Google Search Supported Structured Data Components\n\nThis list tracks which Google Search supported structured data typ"
},
{
"path": "README.md",
"chars": 194069,
"preview": "**Outrank**\n\nGet traffic and outrank competitors with Backlinks & SEO-optimized content while you sleep! I've been keepi"
},
{
"path": "eslint.config.mjs",
"chars": 722,
"preview": "import js from \"@eslint/js\";\nimport globals from \"globals\";\nimport tseslint from \"typescript-eslint\";\nimport pluginReact"
},
{
"path": "examples/app-router-showcase/.gitignore",
"chars": 480,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "examples/app-router-showcase/CLAUDE.md",
"chars": 2170,
"preview": "# Example App Guidelines\n\n## Purpose\n\nThis Next.js app provides real example pages for E2E testing. Each page demonstrat"
},
{
"path": "examples/app-router-showcase/README.md",
"chars": 1450,
"preview": "This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-re"
},
{
"path": "examples/app-router-showcase/app/aggregate-rating/page.tsx",
"chars": 519,
"preview": "import { AggregateRatingJsonLd } from \"next-seo\";\n\nexport default function Page() {\n return (\n <div className=\"conta"
},
{
"path": "examples/app-router-showcase/app/aggregate-rating-restaurant/page.tsx",
"chars": 1348,
"preview": "import { AggregateRatingJsonLd } from \"next-seo\";\n\nexport default function Page() {\n return (\n <div className=\"conta"
},
{
"path": "examples/app-router-showcase/app/article/page.tsx",
"chars": 1311,
"preview": "import { ArticleJsonLd } from \"next-seo\";\n\nexport default function ArticlePage() {\n return (\n <div className=\"contai"
},
{
"path": "examples/app-router-showcase/app/blog-posting/page.tsx",
"chars": 3154,
"preview": "import { ArticleJsonLd } from \"next-seo\";\n\nexport default function BlogPostingPage() {\n return (\n <div className=\"co"
},
{
"path": "examples/app-router-showcase/app/breadcrumb/advanced/page.tsx",
"chars": 3424,
"preview": "import { BreadcrumbJsonLd } from \"next-seo\";\n\nexport default function AdvancedBreadcrumbPage() {\n return (\n <div cla"
},
{
"path": "examples/app-router-showcase/app/breadcrumb/multiple/page.tsx",
"chars": 2947,
"preview": "import { BreadcrumbJsonLd } from \"next-seo\";\n\nexport default function MultipleBreadcrumbsPage() {\n return (\n <div cl"
},
{
"path": "examples/app-router-showcase/app/breadcrumb/page.tsx",
"chars": 2468,
"preview": "import { BreadcrumbJsonLd } from \"next-seo\";\n\nexport default function BreadcrumbPage() {\n return (\n <div className=\""
},
{
"path": "examples/app-router-showcase/app/carousel-course/page.tsx",
"chars": 4563,
"preview": "import { CarouselJsonLd } from \"next-seo\";\n\nexport default function CarouselCoursePage() {\n return (\n <div className"
},
{
"path": "examples/app-router-showcase/app/carousel-movie/page.tsx",
"chars": 7253,
"preview": "import { CarouselJsonLd } from \"next-seo\";\n\nexport default function CarouselMoviePage() {\n return (\n <div className="
},
{
"path": "examples/app-router-showcase/app/carousel-recipe/page.tsx",
"chars": 9792,
"preview": "import { CarouselJsonLd } from \"next-seo\";\n\nexport default function CarouselRecipePage() {\n return (\n <div className"
},
{
"path": "examples/app-router-showcase/app/carousel-restaurant/page.tsx",
"chars": 12282,
"preview": "import { CarouselJsonLd } from \"next-seo\";\n\nexport default function CarouselRestaurantPage() {\n return (\n <div class"
},
{
"path": "examples/app-router-showcase/app/carousel-summary/page.tsx",
"chars": 2625,
"preview": "import { CarouselJsonLd } from \"next-seo\";\n\nexport default function CarouselSummaryPage() {\n return (\n <div classNam"
},
{
"path": "examples/app-router-showcase/app/claim-review/page.tsx",
"chars": 1871,
"preview": "import { ClaimReviewJsonLd } from \"next-seo\";\n\nexport default function ClaimReviewPage() {\n return (\n <div className"
},
{
"path": "examples/app-router-showcase/app/claim-review-advanced/page.tsx",
"chars": 3305,
"preview": "import { ClaimReviewJsonLd } from \"next-seo\";\n\nexport default function ClaimReviewAdvancedPage() {\n return (\n <div c"
},
{
"path": "examples/app-router-showcase/app/claim-review-organization/page.tsx",
"chars": 3462,
"preview": "import { ClaimReviewJsonLd } from \"next-seo\";\n\nexport default function ClaimReviewOrganizationPage() {\n return (\n <d"
},
{
"path": "examples/app-router-showcase/app/course/page.tsx",
"chars": 2356,
"preview": "import { CourseJsonLd } from \"next-seo\";\n\nexport default function CoursePage() {\n return (\n <div className=\"containe"
},
{
"path": "examples/app-router-showcase/app/course-list/page.tsx",
"chars": 3616,
"preview": "import { CourseJsonLd } from \"next-seo\";\n\ninterface CourseProvider {\n name: string;\n sameAs: string;\n}\n\ninterface Cour"
},
{
"path": "examples/app-router-showcase/app/course-list-summary/page.tsx",
"chars": 3945,
"preview": "import { CourseJsonLd } from \"next-seo\";\n\nexport default function CourseListSummaryPage() {\n const courseUrls = [\n \""
},
{
"path": "examples/app-router-showcase/app/creative-work/page.tsx",
"chars": 2481,
"preview": "import { CreativeWorkJsonLd } from \"next-seo\";\n\nexport default function CreativeWorkPage() {\n return (\n <div classNa"
},
{
"path": "examples/app-router-showcase/app/creative-work-blog/page.tsx",
"chars": 5453,
"preview": "import { CreativeWorkJsonLd } from \"next-seo\";\n\nexport default function CreativeWorkBlogPage() {\n return (\n <div cla"
},
{
"path": "examples/app-router-showcase/app/creative-work-multiple/page.tsx",
"chars": 4056,
"preview": "import { CreativeWorkJsonLd } from \"next-seo\";\n\nexport default function CreativeWorkMultiplePage() {\n return (\n <div"
},
{
"path": "examples/app-router-showcase/app/creative-work-news/page.tsx",
"chars": 4564,
"preview": "import { CreativeWorkJsonLd } from \"next-seo\";\n\nexport default function CreativeWorkNewsPage() {\n return (\n <div cla"
},
{
"path": "examples/app-router-showcase/app/custom-podcast/page.tsx",
"chars": 3207,
"preview": "import { PodcastSeriesJsonLd } from \"../../components/custom/PodcastSeriesJsonLd\";\n\nexport default function CustomPodcas"
},
{
"path": "examples/app-router-showcase/app/custom-service/page.tsx",
"chars": 3849,
"preview": "import { ServiceJsonLd } from \"../../components/custom/ServiceJsonLd\";\n\nexport default function CustomServicePage() {\n "
},
{
"path": "examples/app-router-showcase/app/dataset/page.tsx",
"chars": 2121,
"preview": "import { DatasetJsonLd } from \"next-seo\";\n\nexport default function DatasetPage() {\n return (\n <div className=\"contai"
},
{
"path": "examples/app-router-showcase/app/dataset-advanced/page.tsx",
"chars": 8265,
"preview": "import { DatasetJsonLd } from \"next-seo\";\n\nexport default function DatasetAdvancedPage() {\n return (\n <div className"
},
{
"path": "examples/app-router-showcase/app/dataset-catalog/page.tsx",
"chars": 5895,
"preview": "import { DatasetJsonLd } from \"next-seo\";\n\nexport default function DatasetCatalogPage() {\n return (\n <div className="
},
{
"path": "examples/app-router-showcase/app/dataset-nested/page.tsx",
"chars": 9921,
"preview": "import { DatasetJsonLd } from \"next-seo\";\n\nexport default function DatasetNestedPage() {\n return (\n <div className=\""
},
{
"path": "examples/app-router-showcase/app/discussion-forum/page.tsx",
"chars": 1543,
"preview": "import { DiscussionForumPostingJsonLd } from \"next-seo\";\n\nexport default function DiscussionForumPage() {\n return (\n "
},
{
"path": "examples/app-router-showcase/app/discussion-forum-advanced/page.tsx",
"chars": 5572,
"preview": "import { DiscussionForumPostingJsonLd } from \"next-seo\";\n\nexport default function DiscussionForumAdvancedPage() {\n retu"
},
{
"path": "examples/app-router-showcase/app/discussion-forum-deleted/page.tsx",
"chars": 3541,
"preview": "import { DiscussionForumPostingJsonLd } from \"next-seo\";\n\nexport default function DiscussionForumDeletedPage() {\n retur"
},
{
"path": "examples/app-router-showcase/app/employer-aggregate-rating/page.tsx",
"chars": 2879,
"preview": "import { EmployerAggregateRatingJsonLd } from \"next-seo\";\n\nexport default function EmployerAggregateRatingPage() {\n ret"
},
{
"path": "examples/app-router-showcase/app/employer-aggregate-rating-advanced/page.tsx",
"chars": 8660,
"preview": "import { EmployerAggregateRatingJsonLd } from \"next-seo\";\n\nexport default function EmployerAggregateRatingAdvancedPage()"
},
{
"path": "examples/app-router-showcase/app/employer-aggregate-rating-custom-scale/page.tsx",
"chars": 6700,
"preview": "import { EmployerAggregateRatingJsonLd } from \"next-seo\";\n\nexport default function EmployerAggregateRatingCustomScalePag"
},
{
"path": "examples/app-router-showcase/app/event/page.tsx",
"chars": 4164,
"preview": "import { EventJsonLd } from \"next-seo\";\n\nexport default function EventPage() {\n return (\n <div className=\"container "
},
{
"path": "examples/app-router-showcase/app/event-cancelled/page.tsx",
"chars": 3769,
"preview": "import { EventJsonLd } from \"next-seo\";\n\nexport default function EventCancelledPage() {\n return (\n <div className=\"c"
},
{
"path": "examples/app-router-showcase/app/event-free/page.tsx",
"chars": 5837,
"preview": "import { EventJsonLd } from \"next-seo\";\n\nexport default function EventFreePage() {\n return (\n <div className=\"contai"
},
{
"path": "examples/app-router-showcase/app/event-rescheduled/page.tsx",
"chars": 6570,
"preview": "import { EventJsonLd } from \"next-seo\";\n\nexport default function EventRescheduledPage() {\n return (\n <div className="
},
{
"path": "examples/app-router-showcase/app/faq/page.tsx",
"chars": 3404,
"preview": "import { FAQJsonLd } from \"next-seo\";\n\nexport default function FAQPage() {\n return (\n <div className=\"container mx-a"
},
{
"path": "examples/app-router-showcase/app/faq-advanced/page.tsx",
"chars": 7583,
"preview": "import { FAQJsonLd } from \"next-seo\";\n\nexport default function AdvancedFAQPage() {\n return (\n <div className=\"contai"
},
{
"path": "examples/app-router-showcase/app/faq-health/page.tsx",
"chars": 9490,
"preview": "import { FAQJsonLd } from \"next-seo\";\n\nexport default function HealthFAQPage() {\n return (\n <div className=\"containe"
},
{
"path": "examples/app-router-showcase/app/globals.css",
"chars": 608,
"preview": ":root {\n --background: #ffffff;\n --foreground: #171717;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --backg"
},
{
"path": "examples/app-router-showcase/app/howto/page.tsx",
"chars": 4476,
"preview": "import { HowToJsonLd } from \"next-seo\";\n\nexport default function HowToPage() {\n return (\n <div className=\"container "
},
{
"path": "examples/app-router-showcase/app/howto-advanced/page.tsx",
"chars": 8672,
"preview": "import { HowToJsonLd } from \"next-seo\";\n\nexport default function HowToAdvancedPage() {\n return (\n <div className=\"co"
},
{
"path": "examples/app-router-showcase/app/image/page.tsx",
"chars": 1999,
"preview": "import { ImageJsonLd } from \"next-seo\";\n\nexport default function ImagePage() {\n return (\n <div className=\"container "
},
{
"path": "examples/app-router-showcase/app/image-advanced/page.tsx",
"chars": 4292,
"preview": "import { ImageJsonLd } from \"next-seo\";\n\nexport default function ImageAdvancedPage() {\n return (\n <div className=\"co"
},
{
"path": "examples/app-router-showcase/app/image-multiple/page.tsx",
"chars": 7505,
"preview": "import { ImageJsonLd } from \"next-seo\";\n\nexport default function ImageMultiplePage() {\n return (\n <div className=\"co"
},
{
"path": "examples/app-router-showcase/app/job-posting/page.tsx",
"chars": 2858,
"preview": "import { JobPostingJsonLd } from \"next-seo\";\n\nexport default function JobPostingPage() {\n return (\n <div className=\""
},
{
"path": "examples/app-router-showcase/app/job-posting-advanced/page.tsx",
"chars": 6030,
"preview": "import { JobPostingJsonLd } from \"next-seo\";\n\nexport default function AdvancedJobPostingPage() {\n return (\n <div cla"
},
{
"path": "examples/app-router-showcase/app/job-posting-remote/page.tsx",
"chars": 3692,
"preview": "import { JobPostingJsonLd } from \"next-seo\";\n\nexport default function RemoteJobPostingPage() {\n return (\n <div class"
},
{
"path": "examples/app-router-showcase/app/jsonld-test-page/page.tsx",
"chars": 862,
"preview": "// examples/app-router-showcase/app/jsonld-test-page/page.tsx\nimport React from \"react\";\n// Assuming JsonLdScript is exp"
},
{
"path": "examples/app-router-showcase/app/layout.tsx",
"chars": 662,
"preview": "import type { Metadata } from \"next\";\nimport { Geist, Geist_Mono } from \"next/font/google\";\nimport \"./globals.css\";\n\ncon"
},
{
"path": "examples/app-router-showcase/app/local-business/page.tsx",
"chars": 2012,
"preview": "import { LocalBusinessJsonLd } from \"next-seo\";\n\nexport default function LocalBusinessPage() {\n return (\n <div class"
},
{
"path": "examples/app-router-showcase/app/merchant-return-policy/page.tsx",
"chars": 2410,
"preview": "import { MerchantReturnPolicyJsonLd } from \"next-seo\";\n\nexport default function MerchantReturnPolicyPage() {\n return (\n"
},
{
"path": "examples/app-router-showcase/app/merchant-return-policy-advanced/page.tsx",
"chars": 4858,
"preview": "import { MerchantReturnPolicyJsonLd } from \"next-seo\";\n\nexport default function MerchantReturnPolicyAdvancedPage() {\n r"
},
{
"path": "examples/app-router-showcase/app/merchant-return-policy-link/page.tsx",
"chars": 2412,
"preview": "import { MerchantReturnPolicyJsonLd } from \"next-seo\";\n\nexport default function MerchantReturnPolicyLinkPage() {\n retur"
},
{
"path": "examples/app-router-showcase/app/mobile-app/page.tsx",
"chars": 14642,
"preview": "import { SoftwareApplicationJsonLd } from \"next-seo\";\n\nexport default function MobileAppPage() {\n return (\n <div cla"
},
{
"path": "examples/app-router-showcase/app/movie-carousel/page.tsx",
"chars": 3482,
"preview": "import { MovieCarouselJsonLd } from \"next-seo\";\n\nexport default function MovieCarouselPage() {\n return (\n <div class"
},
{
"path": "examples/app-router-showcase/app/movie-carousel-advanced/page.tsx",
"chars": 6578,
"preview": "import { MovieCarouselJsonLd } from \"next-seo\";\n\nexport default function MovieCarouselAdvancedPage() {\n return (\n <d"
},
{
"path": "examples/app-router-showcase/app/movie-carousel-summary/page.tsx",
"chars": 1609,
"preview": "import { MovieCarouselJsonLd } from \"next-seo\";\n\nexport default function MovieCarouselSummaryPage() {\n return (\n <di"
},
{
"path": "examples/app-router-showcase/app/news-article/page.tsx",
"chars": 2511,
"preview": "import { ArticleJsonLd } from \"next-seo\";\n\nexport default function NewsArticlePage() {\n return (\n <div className=\"co"
},
{
"path": "examples/app-router-showcase/app/online-store/page.tsx",
"chars": 6859,
"preview": "import { OrganizationJsonLd } from \"next-seo\";\n\nexport default function OnlineStorePage() {\n return (\n <div classNam"
},
{
"path": "examples/app-router-showcase/app/online-store-loyalty/page.tsx",
"chars": 9591,
"preview": "import { OrganizationJsonLd } from \"next-seo\";\n\nexport default function OnlineStoreLoyaltyPage() {\n return (\n <div c"
},
{
"path": "examples/app-router-showcase/app/organization/page.tsx",
"chars": 2345,
"preview": "import { OrganizationJsonLd } from \"next-seo\";\n\nexport default function OrganizationPage() {\n return (\n <div classNa"
},
{
"path": "examples/app-router-showcase/app/organization-advanced/page.tsx",
"chars": 6542,
"preview": "import { OrganizationJsonLd } from \"next-seo\";\n\nexport default function OrganizationAdvancedPage() {\n return (\n <div"
},
{
"path": "examples/app-router-showcase/app/organization-reviews/page.tsx",
"chars": 3383,
"preview": "import { OrganizationJsonLd } from \"next-seo\";\n\nexport default function OrganizationReviewsPage() {\n return (\n <div "
},
{
"path": "examples/app-router-showcase/app/page.module.css",
"chars": 2747,
"preview": ".page {\n --gray-rgb: 0, 0, 0;\n --gray-alpha-200: rgba(var(--gray-rgb), 0.08);\n --gray-alpha-100: rgba(var(--gray-rgb)"
},
{
"path": "examples/app-router-showcase/app/page.tsx",
"chars": 2692,
"preview": "import Image from \"next/image\";\nimport styles from \"./page.module.css\";\n\nexport default function Home() {\n return (\n "
},
{
"path": "examples/app-router-showcase/app/product/page.tsx",
"chars": 5677,
"preview": "import { ProductJsonLd } from \"next-seo\";\n\nexport default function ProductPage() {\n return (\n <div className=\"contai"
},
{
"path": "examples/app-router-showcase/app/product-3d-model/page.tsx",
"chars": 11088,
"preview": "import { ProductJsonLd } from \"next-seo\";\n\nexport default function Product3DModelPage() {\n return (\n <div className="
},
{
"path": "examples/app-router-showcase/app/product-aggregate/page.tsx",
"chars": 12965,
"preview": "import { ProductJsonLd } from \"next-seo\";\n\nexport default function ProductAggregatePage() {\n return (\n <div classNam"
},
{
"path": "examples/app-router-showcase/app/product-certification/page.tsx",
"chars": 10201,
"preview": "import { ProductJsonLd } from \"next-seo\";\n\nexport default function ProductCertificationPage() {\n return (\n <div clas"
},
{
"path": "examples/app-router-showcase/app/product-member-pricing/page.tsx",
"chars": 9185,
"preview": "import { ProductJsonLd } from \"next-seo\";\n\nexport default function ProductMemberPricingPage() {\n return (\n <div clas"
},
{
"path": "examples/app-router-showcase/app/product-review/page.tsx",
"chars": 11618,
"preview": "import { ProductJsonLd } from \"next-seo\";\n\nexport default function ProductReviewPage() {\n return (\n <div className=\""
},
{
"path": "examples/app-router-showcase/app/product-sale-pricing/page.tsx",
"chars": 5936,
"preview": "import { ProductJsonLd } from \"next-seo\";\n\nexport default function ProductSalePricingPage() {\n return (\n <div classN"
},
{
"path": "examples/app-router-showcase/app/product-shipping-options/page.tsx",
"chars": 12702,
"preview": "import { ProductJsonLd } from \"next-seo\";\n\nexport default function ProductShippingOptionsPage() {\n return (\n <div cl"
},
{
"path": "examples/app-router-showcase/app/product-unit-pricing/page.tsx",
"chars": 8154,
"preview": "import { ProductJsonLd } from \"next-seo\";\n\nexport default function ProductUnitPricingPage() {\n return (\n <div classN"
},
{
"path": "examples/app-router-showcase/app/product-variants/page.tsx",
"chars": 10231,
"preview": "import { ProductJsonLd } from \"next-seo\";\n\nexport default function ProductVariantsPage() {\n return (\n <div className"
},
{
"path": "examples/app-router-showcase/app/product-variants-advanced/page.tsx",
"chars": 17026,
"preview": "import { ProductJsonLd } from \"next-seo\";\n\nexport default function ProductVariantsAdvancedPage() {\n return (\n <div c"
},
{
"path": "examples/app-router-showcase/app/product-variants-multipage/page.tsx",
"chars": 12900,
"preview": "import { ProductJsonLd } from \"next-seo\";\n\nexport default function ProductVariantsMultipagePage() {\n return (\n <div "
},
{
"path": "examples/app-router-showcase/app/product-with-return-policy/page.tsx",
"chars": 5502,
"preview": "import { ProductJsonLd } from \"next-seo\";\n\nexport default function ProductWithReturnPolicyPage() {\n return (\n <div c"
},
{
"path": "examples/app-router-showcase/app/profile/page.tsx",
"chars": 1649,
"preview": "import { ProfilePageJsonLd } from \"next-seo\";\n\nexport default function ProfilePage() {\n return (\n <div className=\"co"
},
{
"path": "examples/app-router-showcase/app/profile-advanced/page.tsx",
"chars": 4833,
"preview": "import { ProfilePageJsonLd } from \"next-seo\";\n\nexport default function AdvancedProfilePage() {\n return (\n <div class"
},
{
"path": "examples/app-router-showcase/app/profile-organization/page.tsx",
"chars": 6258,
"preview": "import { ProfilePageJsonLd } from \"next-seo\";\n\nexport default function OrganizationProfilePage() {\n return (\n <div c"
},
{
"path": "examples/app-router-showcase/app/quiz/page.tsx",
"chars": 1235,
"preview": "import { QuizJsonLd } from \"next-seo\";\n\nexport default function QuizPage() {\n return (\n <div className=\"container mx"
},
{
"path": "examples/app-router-showcase/app/quiz-advanced/page.tsx",
"chars": 4498,
"preview": "import { QuizJsonLd } from \"next-seo\";\n\nexport default function AdvancedQuizPage() {\n return (\n <div className=\"cont"
},
{
"path": "examples/app-router-showcase/app/quiz-biology/page.tsx",
"chars": 2400,
"preview": "import { QuizJsonLd } from \"next-seo\";\n\nexport default function BiologyQuizPage() {\n return (\n <div className=\"conta"
},
{
"path": "examples/app-router-showcase/app/recipe/page.tsx",
"chars": 3622,
"preview": "import { RecipeJsonLd } from \"next-seo\";\n\nexport default function RecipePage() {\n return (\n <div className=\"containe"
},
{
"path": "examples/app-router-showcase/app/recipe-advanced/page.tsx",
"chars": 9124,
"preview": "import { RecipeJsonLd } from \"next-seo\";\n\nexport default function RecipeAdvancedPage() {\n return (\n <div className=\""
},
{
"path": "examples/app-router-showcase/app/restaurant/page.tsx",
"chars": 5080,
"preview": "import { LocalBusinessJsonLd } from \"next-seo\";\n\nexport default function RestaurantPage() {\n return (\n <div classNam"
},
{
"path": "examples/app-router-showcase/app/review/page.tsx",
"chars": 615,
"preview": "import { ReviewJsonLd } from \"next-seo\";\n\nexport default function Page() {\n return (\n <div className=\"container mx-a"
},
{
"path": "examples/app-router-showcase/app/review-advanced/page.tsx",
"chars": 2751,
"preview": "import { ProductJsonLd } from \"next-seo\";\n\nexport default function Page() {\n return (\n <div className=\"container mx-"
},
{
"path": "examples/app-router-showcase/app/review-movie/page.tsx",
"chars": 1916,
"preview": "import { ReviewJsonLd } from \"next-seo\";\n\nexport default function Page() {\n return (\n <div className=\"container mx-a"
},
{
"path": "examples/app-router-showcase/app/social-media-posting/page.tsx",
"chars": 4537,
"preview": "import { DiscussionForumPostingJsonLd } from \"next-seo\";\n\nexport default function SocialMediaPostingPage() {\n return (\n"
},
{
"path": "examples/app-router-showcase/app/software-app/page.tsx",
"chars": 5220,
"preview": "import { SoftwareApplicationJsonLd } from \"next-seo\";\n\nexport default function SoftwareAppPage() {\n return (\n <div c"
},
{
"path": "examples/app-router-showcase/app/software-app-paid/page.tsx",
"chars": 10060,
"preview": "import { SoftwareApplicationJsonLd } from \"next-seo\";\n\nexport default function SoftwareAppPaidPage() {\n return (\n <d"
},
{
"path": "examples/app-router-showcase/app/store-with-departments/page.tsx",
"chars": 6237,
"preview": "import { LocalBusinessJsonLd } from \"next-seo\";\n\nexport default function StoreWithDepartmentsPage() {\n return (\n <di"
},
{
"path": "examples/app-router-showcase/app/test-arrays/page.tsx",
"chars": 1565,
"preview": "import { ArticleJsonLd } from \"next-seo\";\n\nexport default function TestArraysPage() {\n return (\n <div className=\"con"
},
{
"path": "examples/app-router-showcase/app/test-nested/page.tsx",
"chars": 1417,
"preview": "import { RecipeJsonLd } from \"next-seo\";\n\nexport default function TestNestedPage() {\n return (\n <div className=\"cont"
},
{
"path": "examples/app-router-showcase/app/test-url-params/page.tsx",
"chars": 1753,
"preview": "import { ArticleJsonLd } from \"next-seo\";\n\nexport default function TestUrlParamsPage() {\n return (\n <div className=\""
},
{
"path": "examples/app-router-showcase/app/vacation-rental/page.tsx",
"chars": 1326,
"preview": "import { VacationRentalJsonLd } from \"next-seo\";\n\nexport default function VacationRentalPage() {\n return (\n <div cla"
},
{
"path": "examples/app-router-showcase/app/vacation-rental-advanced/page.tsx",
"chars": 8761,
"preview": "import { VacationRentalJsonLd } from \"next-seo\";\n\nexport default function VacationRentalAdvancedPage() {\n return (\n "
},
{
"path": "examples/app-router-showcase/app/vacation-rental-apartment/page.tsx",
"chars": 4786,
"preview": "import { VacationRentalJsonLd } from \"next-seo\";\n\nexport default function VacationRentalApartmentPage() {\n return (\n "
},
{
"path": "examples/app-router-showcase/app/video/page.tsx",
"chars": 1905,
"preview": "import { VideoJsonLd } from \"next-seo\";\n\nexport default function VideoPage() {\n return (\n <div className=\"container "
},
{
"path": "examples/app-router-showcase/app/video-advanced/page.tsx",
"chars": 3364,
"preview": "import { VideoJsonLd } from \"next-seo\";\n\nexport default function VideoAdvancedPage() {\n return (\n <div className=\"co"
},
{
"path": "examples/app-router-showcase/app/video-clips/page.tsx",
"chars": 6122,
"preview": "import { VideoJsonLd } from \"next-seo\";\n\nexport default function VideoClipsPage() {\n return (\n <div className=\"conta"
},
{
"path": "examples/app-router-showcase/app/video-game/page.tsx",
"chars": 16667,
"preview": "import { SoftwareApplicationJsonLd } from \"next-seo\";\n\nexport default function VideoGamePage() {\n return (\n <div cla"
},
{
"path": "examples/app-router-showcase/app/video-live/page.tsx",
"chars": 3719,
"preview": "import { VideoJsonLd } from \"next-seo\";\n\nexport default function VideoLivePage() {\n return (\n <div className=\"contai"
},
{
"path": "examples/app-router-showcase/app/video-seekto/page.tsx",
"chars": 4622,
"preview": "import { VideoJsonLd } from \"next-seo\";\n\nexport default function VideoSeekToPage() {\n return (\n <div className=\"cont"
},
{
"path": "examples/app-router-showcase/app/web-app/page.tsx",
"chars": 13499,
"preview": "import { SoftwareApplicationJsonLd } from \"next-seo\";\n\nexport default function WebAppPage() {\n return (\n <div classN"
},
{
"path": "examples/app-router-showcase/components/custom/PodcastSeriesJsonLd.tsx",
"chars": 1664,
"preview": "\"use client\";\n\nimport { JsonLdScript, processors } from \"next-seo\";\n\ninterface PodcastEpisode {\n name: string;\n durati"
},
{
"path": "examples/app-router-showcase/components/custom/ServiceJsonLd.tsx",
"chars": 1769,
"preview": "\"use client\";\n\nimport { JsonLdScript, processors, type Organization } from \"next-seo\";\n\ninterface ServiceJsonLdProps {\n "
},
{
"path": "examples/app-router-showcase/eslint.config.mjs",
"chars": 466,
"preview": "import { dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\nimport { FlatCompat } from \"@eslint/eslintrc\";\n\ncon"
},
{
"path": "examples/app-router-showcase/next.config.ts",
"chars": 133,
"preview": "import type { NextConfig } from \"next\";\n\nconst nextConfig: NextConfig = {\n /* config options here */\n};\n\nexport default"
},
{
"path": "examples/app-router-showcase/package.json",
"chars": 560,
"preview": "{\n \"name\": \"app-router-showcase\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"next dev --turbop"
},
{
"path": "examples/app-router-showcase/tsconfig.json",
"chars": 632,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"ES2017\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n "
},
{
"path": "package.json",
"chars": 3203,
"preview": "{\n \"name\": \"next-seo\",\n \"version\": \"7.2.0\",\n \"description\": \"SEO plugin for Next.js projects\",\n \"sideEffects\": false"
},
{
"path": "playwright.config.ts",
"chars": 976,
"preview": "import { defineConfig, devices } from \"@playwright/test\";\n\nexport default defineConfig({\n testDir: \"./tests/e2e\",\n ful"
},
{
"path": "pnpm-workspace.yaml",
"chars": 34,
"preview": "packages:\n - '.'\n - 'examples/*'"
},
{
"path": "repomix.config.json",
"chars": 278,
"preview": "{\n \"output\": {\n \"style\": \"xml\",\n \"filePath\": \"repomix-output.xml\"\n },\n \"ignore\": {\n \"customPatterns\": [\n "
},
{
"path": "src/components/.gitkeep",
"chars": 1,
"preview": " "
},
{
"path": "src/components/AggregateRatingJsonLd.test.tsx",
"chars": 2143,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport AggregateRatingJs"
},
{
"path": "src/components/AggregateRatingJsonLd.tsx",
"chars": 1222,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { AggregateRatingJsonLdProps } from \"~/types/review.type"
},
{
"path": "src/components/ArticleJsonLd.test.tsx",
"chars": 12282,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport ArticleJsonLd fro"
},
{
"path": "src/components/ArticleJsonLd.tsx",
"chars": 1566,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { ArticleJsonLdProps } from \"~/types/article.types\";\nimp"
},
{
"path": "src/components/BreadcrumbJsonLd.test.tsx",
"chars": 6291,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport BreadcrumbJsonLd "
},
{
"path": "src/components/BreadcrumbJsonLd.tsx",
"chars": 1304,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { BreadcrumbJsonLdProps } from \"~/types/breadcrumb.types"
},
{
"path": "src/components/CLAUDE.md",
"chars": 4208,
"preview": "# Component Implementation Guidelines\n\n## Critical Pattern: @type Optional\n\n**NEVER require developers to specify `@type"
},
{
"path": "src/components/CarouselJsonLd.test.tsx",
"chars": 16578,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport CarouselJsonLd fr"
},
{
"path": "src/components/CarouselJsonLd.tsx",
"chars": 7251,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type {\n CarouselJsonLdProps,\n CarouselListItem,\n SummaryPa"
},
{
"path": "src/components/ClaimReviewJsonLd.test.tsx",
"chars": 9432,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport ClaimReviewJsonLd"
},
{
"path": "src/components/ClaimReviewJsonLd.tsx",
"chars": 863,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { ClaimReviewJsonLdProps } from \"~/types/claimreview.typ"
},
{
"path": "src/components/CourseJsonLd.test.tsx",
"chars": 10576,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport CourseJsonLd from"
},
{
"path": "src/components/CourseJsonLd.tsx",
"chars": 2465,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type {\n CourseJsonLdProps,\n CourseListItem,\n SummaryPageIt"
},
{
"path": "src/components/CreativeWorkJsonLd.test.tsx",
"chars": 15841,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport CreativeWorkJsonL"
},
{
"path": "src/components/CreativeWorkJsonLd.tsx",
"chars": 2485,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { CreativeWorkJsonLdProps } from \"~/types/creativework.t"
},
{
"path": "src/components/DatasetJsonLd.test.tsx",
"chars": 15064,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport DatasetJsonLd fro"
},
{
"path": "src/components/DatasetJsonLd.tsx",
"chars": 2483,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { DatasetJsonLdProps } from \"~/types/dataset.types\";\nimp"
},
{
"path": "src/components/DiscussionForumPostingJsonLd.test.tsx",
"chars": 15488,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport DiscussionForumPo"
},
{
"path": "src/components/DiscussionForumPostingJsonLd.tsx",
"chars": 1862,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { DiscussionForumPostingJsonLdProps } from \"~/types/disc"
},
{
"path": "src/components/EmployerAggregateRatingJsonLd.test.tsx",
"chars": 10568,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport EmployerAggregate"
},
{
"path": "src/components/EmployerAggregateRatingJsonLd.tsx",
"chars": 2773,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { EmployerAggregateRatingJsonLdProps } from \"~/types/emp"
},
{
"path": "src/components/EventJsonLd.test.tsx",
"chars": 12647,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport EventJsonLd from "
},
{
"path": "src/components/EventJsonLd.tsx",
"chars": 1462,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { EventJsonLdProps } from \"~/types/event.types\";\nimport "
},
{
"path": "src/components/FAQJsonLd.test.tsx",
"chars": 7340,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport FAQJsonLd from \"."
},
{
"path": "src/components/FAQJsonLd.tsx",
"chars": 1753,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type {\n FAQJsonLdProps,\n QuestionInput,\n Question,\n Answe"
},
{
"path": "src/components/HowToJsonLd.test.tsx",
"chars": 20454,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport HowToJsonLd from "
},
{
"path": "src/components/HowToJsonLd.tsx",
"chars": 1635,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { HowToJsonLdProps } from \"~/types/howto.types\";\nimport "
},
{
"path": "src/components/ImageJsonLd.test.tsx",
"chars": 7517,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport I"
},
{
"path": "src/components/ImageJsonLd.tsx",
"chars": 1893,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { ImageJsonLdProps, ImageMetadata } from \"~/types/image."
},
{
"path": "src/components/JobPostingJsonLd.test.tsx",
"chars": 17627,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport JobPostingJsonLd "
},
{
"path": "src/components/JobPostingJsonLd.tsx",
"chars": 2606,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { JobPostingJsonLdProps } from \"~/types/jobposting.types"
},
{
"path": "src/components/LocalBusinessJsonLd.test.tsx",
"chars": 19291,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport LocalBusinessJson"
},
{
"path": "src/components/LocalBusinessJsonLd.tsx",
"chars": 5781,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { LocalBusinessJsonLdProps } from \"~/types/localbusiness"
},
{
"path": "src/components/MerchantReturnPolicyJsonLd.test.tsx",
"chars": 12121,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport MerchantReturnPol"
},
{
"path": "src/components/MerchantReturnPolicyJsonLd.tsx",
"chars": 3841,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { MerchantReturnPolicyJsonLdProps } from \"~/types/mercha"
},
{
"path": "src/components/MovieCarouselJsonLd.test.tsx",
"chars": 10533,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport MovieCarouselJson"
},
{
"path": "src/components/MovieCarouselJsonLd.tsx",
"chars": 2290,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type {\n MovieCarouselJsonLdProps,\n MovieListItem,\n Summary"
},
{
"path": "src/components/OrganizationJsonLd.test.tsx",
"chars": 30646,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport OrganizationJsonL"
},
{
"path": "src/components/OrganizationJsonLd.tsx",
"chars": 3062,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { OrganizationJsonLdProps } from \"~/types/organization.t"
},
{
"path": "src/components/ProductJsonLd.test.tsx",
"chars": 24976,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport ProductJsonLd fro"
},
{
"path": "src/components/ProductJsonLd.tsx",
"chars": 9175,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { ProductJsonLdProps } from \"~/types/product.types\";\nimp"
},
{
"path": "src/components/ProfilePageJsonLd.test.tsx",
"chars": 9890,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport ProfilePageJsonLd"
},
{
"path": "src/components/ProfilePageJsonLd.tsx",
"chars": 1638,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { ProfilePageJsonLdProps } from \"~/types/profile.types\";"
},
{
"path": "src/components/QuizJsonLd.test.tsx",
"chars": 7928,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport QuizJsonLd from \""
},
{
"path": "src/components/QuizJsonLd.tsx",
"chars": 2763,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type {\n QuizJsonLdProps,\n QuestionInput,\n Question,\n Answ"
},
{
"path": "src/components/RecipeJsonLd.test.tsx",
"chars": 19313,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport RecipeJsonLd from"
},
{
"path": "src/components/RecipeJsonLd.tsx",
"chars": 1871,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { RecipeJsonLdProps } from \"~/types/recipe.types\";\nimpor"
},
{
"path": "src/components/ReviewJsonLd.test.tsx",
"chars": 3749,
"preview": "import { render } from \"@testing-library/react\";\nimport { describe, it, expect } from \"vitest\";\nimport ReviewJsonLd from"
},
{
"path": "src/components/ReviewJsonLd.tsx",
"chars": 1577,
"preview": "import { JsonLdScript } from \"~/core/JsonLdScript\";\nimport type { ReviewJsonLdProps } from \"~/types/review.types\";\nimpor"
}
]
// ... and 86 more files (download for full content)
About this extraction
This page contains the full source code of the garmeeh/next-seo GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 286 files (1.8 MB), approximately 444.5k tokens, and a symbol index with 544 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.