mcpField
S
seo-technical

seo-technical

github.com/andrehfp营销与增长7437 次安装40 周前更新
代码仓库

关于 seo-technical

面向 Next.js App Router 项目的技术 SEO 实施指南,适用于开发者。涵盖站点地图、爬虫规则、元数据、结构化数据与上线检查。

主要功能

  1. 01生成动态站点地图
  2. 02配置 robots.txt 爬虫规则
  3. 03设置页面元数据与社交卡片
  4. 04添加 JSON-LD 结构化数据
  5. 05管理规范网址与 SEO 检查清单

适用场景

  1. 01为动态页面生成站点地图
  2. 02按环境配置爬虫访问规则
  3. 03完善页面搜索与社交分享信息
  4. 04上线前检查技术 SEO 配置

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/andrehfp/tinyplate --skill seo-technical

安装后在 Claude Code 中输入/seo-technical确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
checklist.md
nextjs-implementation.md
structured-data.md

详细介绍

技术 SEO 实现(Next.js 2025)

技能文件

此技能包含多个参考文件:

  • SKILL.md(此文件):技术 SEO 核心实现指南
  • nextjs-implementation.md:Next.js 专用代码模板和模式
  • checklist.md:上线前技术 SEO 检查清单
  • structured-data.md:JSON-LD 结构化数据标记模板

此技能涵盖的内容

  1. 站点地图 → app/sitemap.ts,用于动态生成站点地图
  2. Robots.txt → app/robots.ts,用于爬虫指令
  3. 元标签 → OpenGraph、Twitter Cards、关键词、描述
  4. 结构化数据 → 用于丰富摘要的 JSON-LD
  5. 规范 URL → 防止重复内容问题
  6. 性能 SEO → Core Web Vitals 注意事项

第 1 部分:站点地图实现

Next.js App Router 站点地图(app/sitemap.ts)

创建 app/sitemap.ts 后,Next.js 会自动提供 /sitemap.xml:

import type { MetadataRoute } from "next";

const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";

export default function sitemap(): MetadataRoute.Sitemap {
  const currentDate = new Date().toISOString();

  // Static pages
  const staticPages: MetadataRoute.Sitemap = [
    {
      url: BASE_URL,
      lastModified: currentDate,
      changeFrequency: "weekly",
      priority: 1.0,
    },
    {
      url: `${BASE_URL}/pricing`,
      lastModified: currentDate,
      changeFrequency: "monthly",
      priority: 0.8,
    },
    {
      url: `${BASE_URL}/about`,
      lastModified: currentDate,
      changeFrequency: "monthly",
      priority: 0.7,
    },
    {
      url: `${BASE_URL}/privacy`,
      lastModified: currentDate,
      changeFrequency: "yearly",
      priority: 0.3,
    },
    {
      url: `${BASE_URL}/terms`,
      lastModified: currentDate,
      changeFrequency: "yearly",
      priority: 0.3,
    },
  ];

  return staticPages;
}
使用数据库内容生成动态站点地图
import type { MetadataRoute } from "next";
import { db } from "@/lib/db";
import { blogPosts, products } from "@/lib/db/schema";

const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  // Fetch dynamic content
  const posts = await db.select().from(blogPosts).where(eq(blogPosts.published, true));
  const allProducts = await db.select().from(products);

  const staticPages: MetadataRoute.Sitemap = [
    { url: BASE_URL, lastModified: new Date(), changeFrequency: "weekly", priority: 1.0 },
  ];

  const blogPages: MetadataRoute.Sitemap = posts.map((post) => ({
    url: `${BASE_URL}/blog/${post.slug}`,
    lastModified: post.updatedAt || post.createdAt,
    changeFrequency: "weekly" as const,
    priority: 0.7,
  }));

  const productPages: MetadataRoute.Sitemap = allProducts.map((product) => ({
    url: `${BASE_URL}/products/${product.slug}`,
    lastModified: product.updatedAt,
    changeFrequency: "daily" as const,
    priority: 0.8,
  }));

  return [...staticPages, ...blogPages, ...productPages];
}
大型站点地图(50,000+ 个 URL)

使用 generateSitemaps() 生成站点地图索引:

import type { MetadataRoute } from "next";

const URLS_PER_SITEMAP = 50000;

export async function generateSitemaps() {
  const totalProducts = await getProductCount();
  const sitemapCount = Math.ceil(totalProducts / URLS_PER_SITEMAP);

  return Array.from({ length: sitemapCount }, (_, i) => ({ id: i }));
}

export default async function sitemap({ id }: { id: number }): Promise<MetadataRoute.Sitemap> {
  const start = id * URLS_PER_SITEMAP;
  const products = await getProducts({ start, limit: URLS_PER_SITEMAP });

  return products.map((product) => ({
    url: `${BASE_URL}/products/${product.slug}`,
    lastModified: product.updatedAt,
  }));
}
站点地图最佳实践
实践原因
确保 lastModified 准确持续准确时,Google 会使用该值
仅包含规范 URL重复内容会浪费抓取预算
优先级:主页为 1.0,关键页面为 0.8-0.9,其他页面为 0.6-0.7指示爬虫页面的重要性
Google 会忽略 changeFrequency可为其他搜索引擎提供此项
每个站点地图最多包含 50,000 个 URL超出时使用站点地图索引

第 2 部分:Robots.txt 实现

Next.js App Router Robots(app/robots.ts)

import type { MetadataRoute } from "next";

const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";

export default function robots(): MetadataRoute.Robots {
  const isProduction = process.env.NODE_ENV === "production";

  // Block everything in non-production
  if (!isProduction) {
    return {
      rules: { userAgent: "*", disallow: "/" },
    };
  }

  return {
    rules: [
      {
        userAgent: "*",
        allow: "/",
        disallow: [
          "/api/",
          "/dashboard/",
          "/admin/",
          "/private/",
          "/_next/",
          "/sign-in/",
          "/sign-up/",
        ],
      },
      // Block AI training bots (optional)
      { userAgent: "GPTBot", disallow: "/" },
      { userAgent: "ChatGPT-User", disallow: "/" },
      { userAgent: "CCBot", disallow: "/" },
      { userAgent: "anthropic-ai", disallow: "/" },
      { userAgent: "Google-Extended", disallow: "/" },
    ],
    sitemap: `${BASE_URL}/sitemap.xml`,
  };
}
Robots.txt 规则
指令用途
User-agent: *适用于所有爬虫
Allow: /允许抓取此路径
Disallow: /private/禁止抓取此路径
Sitemap:指明站点地图位置
Crawl-delay:放慢抓取速度(Google 不遵守此项)
可禁止/允许的常见 AI 机器人
// Block AI training (keeps content out of training data)
{ userAgent: "GPTBot", disallow: "/" },           // OpenAI
{ userAgent: "ChatGPT-User", disallow: "/" },     // ChatGPT browsing
{ userAgent: "CCBot", disallow: "/" },            // Common Crawl
{ userAgent: "anthropic-ai", disallow: "/" },     // Anthropic
{ userAgent: "Google-Extended", disallow: "/" },  // Google AI training
{ userAgent: "Bytespider", disallow: "/" },       // ByteDance

// Allow AI search (keeps content in AI search results)
// Comment out the above to allow AI indexing
不要禁止抓取的内容
  • 不要禁止抓取 /sitemap.xml
  • 不要禁止抓取 CSS/JS 文件(/_next/static/)
  • 不要禁止抓取希望被编入索引的图片
  • 不要禁止抓取主页

第 3 部分:元数据实现

根布局元数据(app/layout.tsx)

import type { Metadata, Viewport } from "next";

const BASE_URL = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com";

export const viewport: Viewport = {
  width: "device-width",
  initialScale: 1,
  themeColor: "#6366f1",
};

export const metadata: Metadata = {
  metadataBase: new URL(BASE_URL),

  // Title template for child pages
  title: {
    default: "Brand Name — Tagline",
    template: "%s | Brand Name",
  },

  // Description (150-160 chars ideal)
  description: "Your compelling meta description that includes primary keywords and encourages clicks.",

  // Keywords (less important now, but include)
  keywords: ["primary keyword", "secondary keyword", "brand name"],

  // Author info
  authors: [{ name: "Brand Name" }],
  creator: "Brand Name",
  publisher: "Brand Name",

  // Robots directives
  robots: {
    index: true,
    follow: true,
    googleBot: {
      index: true,
      follow: true,
      "max-video-preview": -1,
      "max-image-preview": "large",
      "max-snippet": -1,
    },
  },

  // OpenGraph (Facebook, LinkedIn, etc.)
  openGraph: {
    type: "website",
    locale: "en_US",
    url: BASE_URL,
    siteName: "Brand Name",
    title: "Brand Name — Tagline",
    description: "Your compelling description for social sharing.",
    images: [
      {
        url: "/og-image.png",
        width: 1200,
        height: 630,
        alt: "Brand Name - Description",
      },
    ],
  },

  // Twitter Card
  twitter: {
    card: "summary_large_image",
    title: "Brand Name — Tagline",
    description: "Your compelling description for Twitter.",
    images: ["/og-image.png"],
    creator: "@twitterhandle",
    site: "@twitterhandle",
  },

  // Canonical URL
  alternates: {
    canonical: BASE_URL,
  },

  // App categorization
  category: "Technology",

  // Verification codes
  verification: {
    google: "google-site-verification-code",
    // yandex: "yandex-verification-code",
    // bing: "bing-verification-code",
  },
};

页面级元数据

// app/pricing/page.tsx
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: "Pricing", // Becomes "Pricing | Brand Name" via template
  description: "Simple, transparent pricing. Start free, upgrade when you need more.",
  openGraph: {
    title: "Pricing | Brand Name",
    description: "Simple, transparent pricing. Start free, upgrade when you need more.",
  },
};

export default function PricingPage() {
  // ...
}

动态元数据(generateMetadata)

// app/blog/[slug]/page.tsx
import type { Metadata } from "next";

type Props = {
  params: Promise<{ slug: string }>;
};

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPostBySlug(slug);

  if (!post) {
    return { title: "Post Not Found" };
  }

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: "article",
      publishedTime: post.publishedAt,
      modifiedTime: post.updatedAt,
      authors: [post.author.name],
      images: [
        {
          url: post.coverImage,
          width: 1200,
          height: 630,
          alt: post.title,
        },
      ],
    },
    twitter: {
      card: "summary_large_image",
      title: post.title,
      description: post.excerpt,
      images: [post.coverImage],
    },
    alternates: {
      canonical: `${BASE_URL}/blog/${slug}`,
    },
  };
}

第 4 部分:身份验证中间件集成

使用身份验证(Clerk、NextAuth 等)时,将 SEO 路由添加到公开匹配器:

Clerk(proxy.ts 或 middleware.ts)

import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";

const isPublicRoute = createRouteMatcher([
  "/",
  "/sign-in(.*)",
  "/sign-up(.*)",
  "/pricing",
  "/about",
  "/blog(.*)",
  "/terms",
  "/privacy",
  // SEO files - IMPORTANT!
  "/robots.txt",
  "/sitemap.xml",
  "/sitemap(.*).xml",
  // Icons
  "/icon(.*)",
  "/apple-icon(.*)",
  "/favicon.ico",
]);

export const proxy = clerkMiddleware(async (auth, request) => {
  if (!isPublicRoute(request)) {
    await auth.protect();
  }
});

export const config = {
  matcher: [
    "/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
    "/(api|trpc)(.*)",
  ],
};

NextAuth

export { auth as middleware } from "@/auth";

export const config = {
  matcher: [
    // Exclude SEO files from auth
    "/((?!api|_next/static|_next/image|favicon.ico|robots.txt|sitemap.xml|sitemap.*\\.xml).*)",
  ],
};

第 5 部分:环境变量

SEO 所需的环境变量:

# .env.local (development)
NEXT_PUBLIC_SITE_URL=http://localhost:3000

# .env.production (production)
NEXT_PUBLIC_SITE_URL=https://yourdomain.com

快速参考:文件位置

文件位置用途
站点地图app/sitemap.ts生成 /sitemap.xml
Robotsapp/robots.ts生成 /robots.txt
根元数据app/layout.tsx默认元标签
页面元数据app/[route]/page.tsx页面专属元数据
OG 图片public/og-image.png社交分享图片(1200x630)
网站图标app/icon.tsx 或 public/favicon.ico浏览器标签页图标
Apple 图标app/apple-icon.tsx 或 public/apple-icon.pngiOS 图标

实现检查清单

实现前,请确认:

  1. 已设置 NEXT_PUBLIC_SITE_URL 环境变量
  2. 身份验证中间件允许访问 /robots.txt 和 /sitemap.xml
  3. public/og-image.png 中存在 OG 图片(1200x630px)
  4. 所有公开页面都有唯一的标题和描述
  5. 规范 URL 指向首选版本

实现后,请确认:

  1. 访问 /robots.txt——应显示规则
  2. 访问 /sitemap.xml——应显示 URL
  3. 使用 Google Rich Results Test 进行测试
  4. 使用 Facebook Sharing Debugger 进行测试
  5. 将站点地图提交至 Google Search Console

评论

支持 Markdown