@seo-console/package
v1.3.1
Published
SEO validation and management system for Next.js applications
Maintainers
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 setupThis 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:
- Create
app/api/seo-records/route.tsandapp/api/seo-records/[id]/route.ts - Create
app/api/discover-routes/route.ts - Create
app/api/import-from-site/route.ts - Create
app/api/auto-setup/route.ts(one-click import of all pages) - Create
app/admin/seo/page.tsxwith auto-setup button
After running setup:
- Add a link to
/admin/seoin your admin navigation - Visit
/admin/seoand click "Auto-Setup" to import all existing pages and SEO - 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.jsonThat'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.jsonin your project root (or the path specified inSEO_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:
- Discover routes: Call
POST /api/discover-routesto get a list of all your pages - Import SEO: Call
POST /api/import-from-sitewith 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
- Install:
npm install @seo-console/package - Create API route:
app/api/seo-records/route.ts(see Step 2) - Create admin pages:
app/admin/seo/directory with pages (see Step 3) - Add to navigation: Link to
/admin/seoin your admin menu - Discover & import pages: Create
app/api/discover-routes/route.tsandapp/api/import-from-site/route.ts(see Step 5) - Use in pages: Add
generateMetadatato 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_PATHto customize the file location
License
MIT
