next-seo

repository·main·Indexed 27 days ago

https://github.com/garmeeh/next-seo

An SEO plugin for Next.js projects (version 7.2.0) that simplifies the management of structured data (JSON-LD). It provides specialized components including ArticleJsonLd, ClaimReviewJsonLd, CreativeWorkJsonLd, RecipeJsonLd, and HowToJsonLd to improve search engine visibility and enable rich snippets.

Tokens
43.5K
Snippets
74
Records
128
Agent score
93%

What's inside next-seo

  1. Use DiscussionForumPostingJsonLd for forum and social media posts

    main

    The DiscussionForumPostingJsonLd component is used for forum-style sites where users share perspectives. It supports nested comments for threaded discussions and interaction statistics for engagement metrics.

    Interaction Types

    Supported values for interactionStatistic.interactionType:

    • https://schema.org/LikeAction (Likes/upvotes)
    • https://schema.org/DislikeAction (Dislikes/downvotes)
    • https://schema.org/ViewAction (View count)
    • https://schema.org/CommentAction or https://schema.org/ReplyAction (Comment count)
    • https://schema.org/ShareAction (Share count)

    Best Practices

    • Nested comments: Use the comment array to represent threaded discussions.
    • ISO 8601 dates: Always use proper date formatting with timezone information.
    • Interaction statistics: Add engagement metrics to help search engines understand popularity.
    • Note: For Q&A formats, use Q&A structured data instead.
    <DiscussionForumPostingJsonLd
      headline="Very Popular Thread"
      author={{
        name: "Katie Pope",
        url: "https://example.com/user/katie-pope",
      }}
      datePublished="2024-01-01T08:00:00+00:00"
      text="Look at how cool this concert was!"
      comment={[
        {
          text: "This should not be this popular",
          author: "Commenter One",
          datePublished: "2024-01-01T09:00:00+00:00",
          comment: [
            {
              text: "Yes it should",
              author: "Commenter Two",
              datePublished: "2024-01-01T09:30:00+00:00",
            },
          ],
        },
      ]}
    />
  2. Run quality checks for the project

    main

    Before completing development or deployment, run the following commands to ensure code quality, type safety, and build integrity.

    # 1. Run unit tests
    pnpm test:unit
    
    # 2. Type checking
    pnpm typecheck
    
    # 3. Linting
    pnpm lint
    
    # 4. Build the package
    pnpm build
    
    # Full sweep (optional)
    pnpm test:sweep
  3. Implement a JSON-LD component

    main

    Components should be created in src/components/[Component]JsonLd.tsx.

    Implementation Requirements:

    • Rendering: Use the JsonLdScript component for the final output.
    • Data Construction: Use object spread with conditional inclusion for optional properties (e.g., ...(url && { url })).
    • Input Processing: Always use process functions from ~/utils/processors for properties that accept flexible types (strings, objects without @type).
    • Array Handling: Use .map() to apply process functions to arrays (e.g., author.map(processAuthor)).
    • Defaults: Apply sensible defaults, such as setting dateModified to datePublished if dateModified is not provided.
    • Booleans: Explicitly check boolean props using !== undefined to ensure false values are correctly included in the output.
    // src/components/ArticleJsonLd.tsx
    import { JsonLdScript } from "~/core/JsonLdScript";
    import type { ArticleJsonLdProps } from "~/types/article.types";
    import { processAuthor, processImage } from "~/utils/processors";
    
    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 }),
        ...(!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 };
  4. Test new JSON-LD components

    main

    Create tests in src/components/[Component]JsonLd.test.tsx using vitest and @testing-library/react.

    Testing Checklist:

    • Basic Rendering: Verify rendering with minimal required props.
    • Schema Variations: Test all supported schema type variations (e.g., Article vs NewsArticle).
    • Flexible Inputs: Verify that strings are correctly converted to objects (e.g., author="John Doe" $\rightarrow$ { "@type": "Person", "name": "John Doe" }).
    • Array Handling: Test handling of arrays for properties like author and image.
    • Optional Properties: Ensure all optional properties are correctly included when provided.
    • Defaults: Verify that default values (like dateModified defaulting to datePublished) are applied.
    • Boolean Values: Ensure false values are correctly rendered (e.g., isAccessibleForFree: false).
    • Custom Identifiers: Test custom scriptId and scriptKey usage.
    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
        });
      });
    });
  5. Create custom JSON-LD components

    main

    You can build custom structured data components by combining JsonLdScript with processors. This allows you to provide a simplified API to your users (e.g., accepting a string for an author) while outputting valid, fully-typed Schema.org JSON-LD.

    Quick Start Example

    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" />;
    }
    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
    <PodcastEpisodeJsonLd
      name="Episode 1: Getting Started"
      author="Jane Doe"
      duration="PT30M"
      url="https://example.com/episode-1"
    />;
  6. Implement the @type Optional Pattern for components

    main

    When creating new JSON-LD components, follow the @type Optional Pattern to improve developer experience. This pattern allows users to provide simplified inputs (like strings or objects without @type) while the component automatically injects the correct Schema.org @type during processing.

    Key steps:

    1. Type Definitions: Use Omit<Type, "@type"> in your component props to prevent users from having to manually specify the @type property.
    2. Process Functions: Use utility functions (e.g., processAuthor, processImage) to transform flexible inputs into valid Schema.org objects.
    3. Intelligent Detection: Ensure process functions can detect types based on property analysis (e.g., an object with familyName is treated as a Person).
    // Example of defining props using the Omit pattern
    export type ArticleJsonLdProps = (
      | Omit<Article, "@type">
      | Omit<NewsArticle, "@type">
      | Omit<BlogPosting, "@type">
    ) & {
      type?: "Article" | "NewsArticle" | "BlogPosting";
      scriptId?: string;
      scriptKey?: string;
    };
  7. Create TypeScript definitions for new components

    main

    New components should define their types in src/types/[component].types.ts.

    Guidelines:

    • Reuse Common Types: Always import and reuse shared definitions like ImageObject, Person, Organization, and Author from common.types.ts to avoid duplication.
    • Base Interfaces: Create a base interface for common properties shared across subtypes.
    • Subtype Interfaces: Define specific interfaces for different Schema.org subtypes (e.g., NewsArticle, BlogPosting) that extend the base interface.
    • Flexible Props: Use union types for properties that accept multiple formats (e.g., string | Person | Organization) and support both single items and arrays.
    // src/types/article.types.ts
    import type { ImageObject, Person, Organization, Author } from "./common.types";
    
    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; };
    }
    
    export interface Article extends ArticleBase {
      "@type": "Article";
    }
    
    export type ArticleJsonLdProps = (
      | Omit<Article, "@type">
    ) & {
      type?: "Article";
      scriptId?: string;
      scriptKey?: string;
    };
  8. Create custom JSON-LD components with JsonLdScript and processors

    main

    To create custom structured data components, import JsonLdScript and processors from next-seo. Define a component that constructs a schema object and returns <JsonLdScript data={data} scriptKey={...} />.

    Recommended Workflow:

    1. Define Props: Use TypeScript to define the shape of the data your component accepts.
    2. Use Processors: Instead of requiring users to provide full Schema.org objects (like adding @type: 'Organization'), use processors to handle flexible inputs (e.g., accepting a simple string or a full object).
    3. Apply the @type Optional Pattern: Design your component so that users can pass simple values (like a string for a provider name) while the component internally handles the @type via a processor.
    4. Return JsonLdScript: Pass the constructed object to the data prop of JsonLdScript.
    import { JsonLdScript, processors } from "next-seo";
    
    export function MyCustomComponent({ name, provider }) {
      const data = {
        "@context": "https://schema.org",
        "@type": "Service",
        name,
        provider: processors.processOrganization(provider),
      };
    
      return <JsonLdScript data={data} scriptKey="my-service" />;
    }