npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@seo-console/package

v1.3.1

Published

SEO validation and management system for Next.js applications

Readme

SEO Console Package

A production-ready SEO validation and management system for Next.js applications. This package provides a complete admin interface for managing SEO metadata, accessible through a new tab in your admin section.

Installation

npm install @seo-console/package

🤖 For AI Coders (Cursor, GitHub Copilot, etc.):

After installing, you MUST run the setup script to generate all necessary files:

npx @seo-console/package setup

This automatically creates:

  • ✅ All API routes (/api/seo-records, /api/discover-routes, /api/import-from-site, /api/auto-setup)
  • ✅ Admin page at /admin/seo
  • ✅ Everything needed for the package to work

The setup script will:

  1. Create app/api/seo-records/route.ts and app/api/seo-records/[id]/route.ts
  2. Create app/api/discover-routes/route.ts
  3. Create app/api/import-from-site/route.ts
  4. Create app/api/auto-setup/route.ts (one-click import of all pages)
  5. Create app/admin/seo/page.tsx with auto-setup button

After running setup:

  1. Add a link to /admin/seo in your admin navigation
  2. Visit /admin/seo and click "Auto-Setup" to import all existing pages and SEO
  3. Done! Everything works automatically.

Setup Guide

Step 1: Configure Storage (Optional)

The package uses file storage (no database required). SEO records are stored in seo-records.json in your project root.

To customize the storage location, add to your .env.local:

SEO_CONSOLE_STORAGE_PATH=./data/seo-records.json

That's it! No database setup needed.

Step 3: Create API Routes (AUTOMATIC if you ran setup script)

⚠️ If you ran npx @seo-console/package setup, skip this step! The setup script already created these files.

If you didn't run setup, you MUST create these manually:

Create app/api/seo-records/route.ts:

import { NextRequest, NextResponse } from "next/server";
import { detectStorageConfig, createStorageAdapter } from "@seo-console/package";
import { createSEORecordSchema } from "@seo-console/package/server";

// GET - Fetch all SEO records
export async function GET() {
  try {
    // Get file storage adapter
    const config = detectStorageConfig();
    const storage = createStorageAdapter(config);
    
    // Check if storage is available
    const isAvailable = await storage.isAvailable();
    if (!isAvailable) {
      return NextResponse.json(
        { error: "Storage not available" },
        { status: 500 }
      );
    }
    
    const records = await storage.getRecords();
    return NextResponse.json({ data: records || [] });
  } catch (error) {
    console.error("Error fetching SEO records:", error);
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Failed to fetch records" },
      { status: 500 }
    );
  }
}

// POST - Create a new SEO record
export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    
    // Validate the request body
    const validated = createSEORecordSchema.parse(body);
    
    // Auto-detect storage type
    const config = detectStorageConfig();
    const storage = createStorageAdapter(config);
    
    const record = await storage.createRecord(validated);
    return NextResponse.json({ data: record }, { status: 201 });
  } catch (error) {
    console.error("Error creating SEO record:", error);
    if (error instanceof Error) {
      return NextResponse.json(
        { error: error.message },
        { status: 400 }
      );
    }
    return NextResponse.json(
      { error: "Invalid request" },
      { status: 400 }
    );
  }
}

Create app/api/seo-records/[id]/route.ts:

import { NextRequest, NextResponse } from "next/server";
import { detectStorageConfig, createStorageAdapter } from "@seo-console/package";
import { updateSEORecordSchema } from "@seo-console/package/server";

interface RouteParams {
  params: Promise<{ id: string }>;
}

// GET - Get a single SEO record
export async function GET(
  request: NextRequest,
  { params }: RouteParams
) {
  try {
    const { id } = await params;
    const config = detectStorageConfig();
    const storage = createStorageAdapter(config);
    
    const record = await storage.getRecordById(id);
    
    if (!record) {
      return NextResponse.json(
        { error: "Record not found" },
        { status: 404 }
      );
    }
    
    return NextResponse.json({ data: record });
  } catch (error) {
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Failed to fetch record" },
      { status: 500 }
    );
  }
}

// PATCH - Update an SEO record
export async function PATCH(
  request: NextRequest,
  { params }: RouteParams
) {
  try {
    const { id } = await params;
    const body = await request.json();
    
    const validated = updateSEORecordSchema.parse({ ...body, id });
    const config = detectStorageConfig();
    const storage = createStorageAdapter(config);
    
    const record = await storage.updateRecord(validated);
    return NextResponse.json({ data: record });
  } catch (error) {
    if (error instanceof Error) {
      return NextResponse.json(
        { error: error.message },
        { status: 400 }
      );
    }
    return NextResponse.json(
      { error: "Invalid request" },
      { status: 400 }
    );
  }
}

// DELETE - Delete an SEO record
export async function DELETE(
  request: NextRequest,
  { params }: RouteParams
) {
  try {
    const { id } = await params;
    const config = detectStorageConfig();
    const storage = createStorageAdapter(config);
    
    await storage.deleteRecord(id);
    return NextResponse.json({ success: true });
  } catch (error) {
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Failed to delete record" },
      { status: 500 }
    );
  }
}

Important: These API routes use file storage. SEO records are stored in seo-records.json in your project root (or the path specified in SEO_CONSOLE_STORAGE_PATH).

Step 3: Add Admin Pages

Create the admin SEO section in your Next.js app. This will be accessible as a new tab in your admin area.

Create the directory structure:

app/admin/seo/
  ├── page.tsx          (Main dashboard)
  ├── editor/
  │   └── page.tsx      (SEO record editor)
  ├── reports/
  │   └── page.tsx      (Reports & analytics)
  ├── search/
  │   └── page.tsx      (Search & validation)
  └── settings/
      └── page.tsx      (Settings)

app/admin/seo/page.tsx (Main Dashboard):

"use client";

import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";
import type { SEORecord } from "@seo-console/package";

export default function SEOAdminPage() {
  const router = useRouter();
  const [records, setRecords] = useState<SEORecord[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch("/api/seo-records")
      .then((res) => res.json())
      .then((data) => {
        setRecords(data.data || []);
        setLoading(false);
      })
      .catch(() => setLoading(false));
  }, []);

  return (
    <div>
      <h1>SEO Management</h1>
      <nav>
        <a href="/admin/seo">Dashboard</a>
        <a href="/admin/seo/editor">Editor</a>
        <a href="/admin/seo/reports">Reports</a>
        <a href="/admin/seo/search">Search</a>
        <a href="/admin/seo/settings">Settings</a>
      </nav>
      {/* Your SEO dashboard content */}
    </div>
  );
}

app/admin/seo/editor/page.tsx (Editor):

"use client";

import { useState, useEffect } from "react";
import { useSearchParams } from "next/navigation";
import type { SEORecord } from "@seo-console/package";

export default function EditorPage() {
  const searchParams = useSearchParams();
  const route = searchParams.get("route");
  const [record, setRecord] = useState<SEORecord | null>(null);
  const [formData, setFormData] = useState({
    title: "",
    description: "",
    canonicalUrl: "",
  });

  // Load and save logic here
  return (
    <div>
      <h1>Edit SEO Record</h1>
      {/* Your editor form */}
    </div>
  );
}

Note: For complete implementation examples, see the demo app in the repository. The package provides the data layer and utilities; you'll need to build the UI components or copy from the demo.

Step 5: Add SEO Metadata to Your Pages

Use the useGenerateMetadata hook to automatically generate metadata from your SEO records:

// app/blog/[slug]/page.tsx
import { useGenerateMetadata } from "@seo-console/package/hooks";

export async function generateMetadata({ params }: { params: { slug: string } }) {
  const metadata = await useGenerateMetadata({
    routePath: `/blog/${params.slug}`,
    fallback: {
      title: "Blog Post",
      description: "Default description"
    }
  });
  return metadata;
}

Step 5: Add to Your Admin Navigation

Add a link to the SEO admin section in your main admin navigation:

// app/admin/layout.tsx or your admin navigation component
<nav>
  <Link href="/admin/dashboard">Dashboard</Link>
  <Link href="/admin/seo">SEO</Link> {/* Add this */}
  <Link href="/admin/users">Users</Link>
  {/* ... other admin links */}
</nav>

Step 5: Discover and Import Existing Pages (Optional but Recommended)

⚠️ IMPORTANT: The package doesn't automatically detect your existing pages or SEO settings. You need to discover and import them.

Create app/api/discover-routes/route.ts to find all your pages:

import { NextResponse } from "next/server";
import { discoverNextJSRoutes } from "@seo-console/package/server";

export async function POST() {
  try {
    const appDir = process.env.NEXT_PUBLIC_APP_DIR || "app";
    const routes = await discoverNextJSRoutes(appDir);
    return NextResponse.json({ routes });
  } catch (error) {
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Failed to discover routes" },
      { status: 500 }
    );
  }
}

Create app/api/import-from-site/route.ts to import existing SEO metadata:

import { NextRequest, NextResponse } from "next/server";
import { detectStorageConfig, createStorageAdapter } from "@seo-console/package";
import { extractMetadataFromURL, metadataToSEORecord } from "@seo-console/package/server";

export async function POST(request: NextRequest) {
  try {
    const { baseUrl, routes } = await request.json();

    if (!baseUrl) {
      return NextResponse.json({ error: "Base URL is required" }, { status: 400 });
    }

    const routesToImport = Array.isArray(routes) && routes.length > 0 ? routes : ["/"];
    const results: Array<{ route: string; success: boolean; error?: string }> = [];
    
    const config = detectStorageConfig();
    const storage = createStorageAdapter(config);

    for (const route of routesToImport) {
      try {
        const url = new URL(route, baseUrl).toString();
        const metadata = await extractMetadataFromURL(url);

        if (Object.keys(metadata).length === 0) {
          results.push({ route, success: false, error: "No metadata found" });
          continue;
        }

        const recordData = metadataToSEORecord(metadata, route, "file-user");
        await storage.createRecord(recordData as { userId: string; routePath: string; [key: string]: unknown });
        results.push({ route, success: true });

        // Rate limiting - wait 200ms between requests
        await new Promise((resolve) => setTimeout(resolve, 200));
      } catch (err) {
        results.push({
          route,
          success: false,
          error: err instanceof Error ? err.message : "Unknown error",
        });
      }
    }

    return NextResponse.json({ results });
  } catch (error) {
    return NextResponse.json(
      { error: error instanceof Error ? error.message : "Failed to import from site" },
      { status: 500 }
    );
  }
}

How to use these routes:

  1. Discover routes: Call POST /api/discover-routes to get a list of all your pages
  2. Import SEO: Call POST /api/import-from-site with your site's base URL and the routes you want to import

Example:

// In your admin UI or a script
const discoverResponse = await fetch("/api/discover-routes", { method: "POST" });
const { routes } = await discoverResponse.json();

const importResponse = await fetch("/api/import-from-site", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    baseUrl: "https://yoursite.com",
    routes: routes.map((r: any) => r.routePath)
  })
});

Quick Start Summary

  1. Install: npm install @seo-console/package
  2. Create API route: app/api/seo-records/route.ts (see Step 2)
  3. Create admin pages: app/admin/seo/ directory with pages (see Step 3)
  4. Add to navigation: Link to /admin/seo in your admin menu
  5. Discover & import pages: Create app/api/discover-routes/route.ts and app/api/import-from-site/route.ts (see Step 5)
  6. Use in pages: Add generateMetadata to your pages (see Step 4)

That's it! The SEO admin interface will be accessible at /admin/seo as a new tab in your admin area.

API Reference

Hooks

useGenerateMetadata(options)

Generates Next.js metadata from SEO records.

import { useGenerateMetadata } from "@seo-console/package/hooks";

const metadata = await useGenerateMetadata({
  routePath: "/about",
  fallback: {
    title: "Default Title",
    description: "Default description"
  }
});

Storage Functions

import { 
  detectStorageConfig,
  createStorageAdapter
} from "@seo-console/package";

// Get storage adapter (uses file storage)
const config = detectStorageConfig();
const storage = createStorageAdapter(config);

// Use storage methods
const records = await storage.getRecords();
const record = await storage.getRecordByRoute("/about");
await storage.createRecord({ routePath: "/contact", title: "Contact" });

Other Server-Side Functions

import { 
  generateSitemapFromRecords,
  generateRobotsTxt,
  discoverNextJSRoutes,
  extractMetadataFromURL
} from "@seo-console/package/server";

Storage

The package uses file storage - SEO records are stored in a JSON file (seo-records.json by default).

  • No database required - works out of the box
  • Simple and fast - perfect for most sites
  • Version controlled - the JSON file can be committed to git
  • Configurable - set SEO_CONSOLE_STORAGE_PATH to customize the file location

License

MIT