从“无人问津”到“被搜索引擎发现”:一个程序员的 SEO 实战指南

64 阅读9分钟

从“无人问津”到“被搜索引擎发现”:一个程序员的 SEO 实战指南

引子:一个令人惋惜的故事

你是一个小阿巴,好不容易开发好了一个网站,这时候你满怀欣喜地将它上线。
这时候的你,时不时打开你的网站后台,去查看有没有新注册的用户。

你却发现:我去,怎么还没有人注册啊。
这时候的你再也没有了对网站运营下去的动力,于是狠心点击了阿里云服务器强制关机按钮——
从此,一代优秀的产品就此下线。

image.png


正文开始:为什么你的网站没人用?

首先我们来想一想,我们费劲开发的网站为什么没有人使用?
是产品不够好吗?还是 idea 不行?
肯定和这两个有一定的关系。

但是,我觉得最重要的是:得有人知道你的网站/产品啊!

再好的产品,没有人知道,没有人使用,到最后还是要下线.

我相信这也是很多个人开发者所面临的问题吧。
并且随着 AI 的发展,AI 现在这么火热,个人开发者也是越来越多了。
网站没人用导致没有运营的成本,就会增大网站下线的风险。

有人觉得产品本身的质量也会有影响,但我觉得哈——
直接说最现实的问题:能不能变现!!

有人用才有的赚。

那么怎么有人用呢?
当然,可以投广告、写优质内容什么的。
但是这些对于程序员来说好像都不熟悉。

其实还有一个方式就是:做网站的 SEO 优化。


什么是 SEO?

我们来看解释:

SEO 是一种通过优化网站内容和技术结构,提高网站在搜索引擎(如 Google、百度、Bing)自然排名中的位置,从而获得更多免费流量(自然流量)的技术和过程。

众所周知,搜索引擎,包括百度、必应、谷歌还有一些其他的,都是通过网络爬虫技术实现的,说白了就是网页抓取。

image.png

当我们访问一个网站的时候,在浏览器输入网址 baidu.com
百度的服务器就会返回 HTML 页面,然后浏览器再去解析 HTML 代码,就变成了我们所看到的网页。

那么 HTML 中包含什么东西呢?
在我们做前端开发的时候,比如使用 React,你在网站的顶部导航栏写了一个本项目的 GitHub 地址,这个不就是一个 URL 吗?

所以 HTML 页面中包含很多的 URL。

网络爬虫程序就像一个蜘蛛一样,在互联网这张类似于大蜘蛛网上爬行:
每访问到一个网站,它都会提取出页面的 URL,再去访问这些提取到的 URL,以此类推,
就像函数的递归调用一样,同时还会将这个网址收入到搜索引擎自己的数据库中去。

当然这只是一个简单的概述。


搜索引擎抓取前的关键文件

那具体会怎么做呢?

搜索引擎会先访问该网站的 robots.txt 文件,
文件内容是告诉搜索引擎,哪些资源可以访问,哪些资源不可以访问。

同时站长通常会在网站放一个 Sitemap(站点地图)

所谓站点地图,就是一个文件,里面包含了网页、视频、图片或其他文件有关的信息。
站点地图可以帮助搜索引擎发现、爬取和索引网站的所有内容
它还可以告诉搜索引擎网站上的页面什么时候更新,以及哪些网页和文件比较重要。

Sitemap 最常说的有两种类型:

  • 一个是给用户看的(HTML 站点地图)
  • 一个是给搜索引擎看的(XML 站点地图)

这里我们主要说的是 XML 站点地图
有些站点地图是静态的直接写死的,但是大多网站的站点地图是动态生成的.

比如我开发的 IntelliDraw 画图平台,这里的站点地图就是动态生成的:

image.png

image.png

就拿我这个项目来说,因为我是使用 Next.js 服务端渲染技术实现的,天生就对 SEO 比较友好,尤其是 Next.js 的 MetaData API

这里的 robots.txt 文件也是动态生成的:

image.png


那么,我们自己该如何去优化一个网站的 SEO 呢?

就拿我这个 IntelliDraw 画图平台 举例。

我的优化思路是这样的:先从 robots.txt 这个文件入手

因为是服务端渲染,这个文件就可以写成 TS 代码在服务端动态生成。
这里就用了 Next.js 的 MetadataRoute API,来告诉搜索引擎可访问的资源和不可访问的资源。

我的 robots.ts 代码如下:

import type { MetadataRoute } from "next"

export default function robots(): MetadataRoute.Robots {
  const appUrl = process.env.NEXT_PUBLIC_APP_URL || "http://47.95.35.178"
  return {
    rules: {
      userAgent: "*",
      allow: "/",
      disallow: [
        "/api/",
        "/admin/",
        "/my-diagrams/",
        "/my-spaces/",
        "/my-rooms/",
        "/team-spaces/",
        "/user/",
        "/diagram/edit/", // Prevent editor indexing
      ],
    },
    sitemap: `${appUrl}/sitemap.xml`,
  }
}

在这里也可以直接就把 sitemap 写进去来动态生成。


在 app 目录下新建一个 sitemap.ts 文件

内容如下:

import type { MetadataRoute } from "next"

// Simple interface for the API response
interface MaterialVO {
  id: string
  updateTime?: string
  createTime?: string
}

interface BaseResponsePageMaterialVO {
  code: number
  data?: {
    records: MaterialVO[]
  }
}

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const appUrl = process.env.NEXT_PUBLIC_APP_URL || "http://47.95.35.178"

  // 1. Static Routes
  const routes = [
    "",
    "/about",
    "/templates",
    "/solutions/uml-diagram",
    "/solutions/flowchart",
    "/solutions/db-diagram",
    "/solutions/topology",
    "/solutions/mind-map",
    "/wiki/what-is-uml-diagram",
    "/wiki/how-to-draw-flowchart",
  ].map((route) => ({
    url: `${appUrl}${route}`,
    lastModified: new Date(),
    changeFrequency: "weekly" as const,
    priority: route === "" ? 1 : 0.8,
  }))

  // 2. Dynamic Template Routes
  let templateRoutes: MetadataRoute.Sitemap = []
  try {
    // Fetch latest templates (adjust pageSize as needed, e.g. 100 or 1000)
    // Using the same endpoint as the frontend
    const response = await fetch("http://47.95.35.178:8081/api/material/list/page/vo", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        current: 1,
        pageSize: 100, // Fetch top 100 for sitemap
        sortField: "createTime",
        sortOrder: "desc",
      }),
      next: { revalidate: 3600 }, // Revalidate every hour
    })

    if (response.ok) {
      const json: BaseResponsePageMaterialVO = await response.json()
      if (json.code === 0 && json.data?.records) {
        templateRoutes = json.data.records.map((item) => ({
          url: `${appUrl}/templates/${item.id}`,
          lastModified: item.updateTime ? new Date(item.updateTime) : new Date(),
          changeFrequency: "weekly" as const,
          priority: 0.7,
        }))
      }
    }
  } catch (error) {
    console.error("Sitemap generation failed to fetch templates:", error)
  }

  return [...routes, ...templateRoutes]
}

这里解释一下这个 sitemap.ts 文件

它主要做了以下三件事:

  1. 定义固定页面:手动列出了首页、关于我们、解决方案等固定的基础路径。
  2. 获取动态页面:从后端 API 抓取最新的 100 个“模板”详情页链接,并设定了 1 小时更新一次 的缓存策略,避免频繁请求服务器。
  3. 合并输出:将固定页面和动态抓取的页面合并在一起,告诉搜索引擎(如 Google、百度)你网站上有哪些页面需要被收录。

还有 Static Routes 部分

Static Routes 的作用就是查漏补缺:

  1. 补充数据库盲区:API 只能告诉你有哪些模板 ID,但它不知道你还有 /about/solutions/flowchart 这些固定页面。如果你不手动写上,搜索引擎可能就不会优先爬取这些核心页面。
  2. 定义核心权重:这些静态页面通常是网站的入口页营销页(如首页、产品介绍),对 SEO 至关重要。在代码中,我特意给它们设置了较高的权重(priority: 1 或 0.8),这通常比单独一个模板详情页(priority: 0.7)更重要。

最终生成的 sitemap.xml 文件类似于下面这样的:

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>http://localhost</loc>
    <lastmod>2026-02-02T11:00:00.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>1</priority>
  </url>

  <url>
    <loc>http://localhost/about</loc>
    <lastmod>2026-02-02T11:00:00.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
  </url>
  <url>
    <loc>http://localhost/solutions/uml-diagram</loc>
    <lastmod>2026-02-02T11:00:00.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
  </url>

  <url>
    <loc>http://localhost/templates/temp-id-001</loc>
    <lastmod>2025-12-20T08:30:00.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.7</priority>
  </url>
  <url>
    <loc>http://localhost/templates/temp-id-002</loc>
    <lastmod>2025-12-21T09:15:00.000Z</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.7</priority>
  </url>
</urlset>

然后我在 app 目录下新建了两个路由:一个是 wiki,另一个是 solutions

为什么要有 wiki 目录呢?

我们想一想:你现在是用户,你要去找一个 AI 画图软件,你会怎么搜索?
换个说法,你的搜索词可能是哪些?

比如: “AI 画图网站”  —— 这是最普遍的吧。

还有一种情况呢?当用户搜索: “怎么画 UML 类图?”

这时候是不是搜索引擎就不容易发现我们的网站?
因为我们的网站主要是做 AI 画图,对吧?并没有提供“怎么画啊?”
这明显是一个教程

所以考虑到这个情况,我添加了 wiki 动态路由:

image.png

import { Metadata } from "next"
import { notFound } from "next/navigation"
import { getWikiBySlug, wikiArticles } from "@/lib/wiki-data"
import Link from "next/link"
import ReactMarkdown from "react-markdown"
import remarkGfm from "remark-gfm"
import { Calendar, User, Tag, ArrowRight } from "lucide-react"

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

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

  if (!article) {
    return {
      title: "Article Not Found",
    }
  }

  return {
    title: article.title,
    description: article.description,
    openGraph: {
      title: article.title,
      description: article.description,
      type: "article",
      url: `/wiki/${slug}`,
    },
  }
}

export async function generateStaticParams() {
  return Object.keys(wikiArticles).map((slug) => ({
    slug,
  }))
}

export default async function WikiPage({ params }: Props) {
  const { slug } = await params;
  const article = getWikiBySlug(slug)

  if (!article) {
    notFound()
  }

  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "Article",
    headline: article.title,
    description: article.description,
    author: {
      "@type": "Organization",
      name: "IntelliDraw Team",
    },
    publisher: {
      "@type": "Organization",
      name: "IntelliDraw",
      logo: {
        "@type": "ImageObject",
        url: "http://47.95.35.178/logo.png", // Replace with real logo
      },
    },
    datePublished: "2024-01-01T08:00:00+08:00", // Should be dynamic
  }

  return (
    <div className="min-h-screen bg-white">
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />

      <article className="max-w-4xl mx-auto px-4 sm:px-6 py-12">
        <header className="mb-10 text-center">
          <div className="flex items-center justify-center gap-4 text-sm text-gray-500 mb-4">
            <span className="flex items-center gap-1">
              <Tag className="w-4 h-4" />
              {article.category}
            </span>
            <span className="flex items-center gap-1">
              <User className="w-4 h-4" />
              IntelliDraw Team
            </span>
            <span className="flex items-center gap-1">
              <Calendar className="w-4 h-4" />
              2024-01-01
            </span>
          </div>
          <h1 className="text-3xl sm:text-4xl font-extrabold text-slate-900 mb-6 leading-tight">
            {article.title}
          </h1>
        </header>

        <div className="prose prose-lg prose-blue mx-auto">
          <ReactMarkdown remarkPlugins={[remarkGfm]}>
            {article.content}
          </ReactMarkdown>
        </div>

        {/* Related CTA */}
        <div className="mt-16 p-8 bg-blue-50 rounded-2xl border border-blue-100 text-center">
          <h3 className="text-2xl font-bold text-slate-900 mb-4">
            准备好实践了吗?
          </h3>
          <p className="text-slate-600 mb-8 max-w-2xl mx-auto">
            [IntelliDraw 画图平台](http://47.95.35.178:6001) 提供了文中提到的所有图表工具。现在就开始,释放你的创意。
          </p>
          <Link href="/diagram/new">
            <button className="inline-flex items-center gap-2 bg-blue-600 hover:bg-blue-700 text-white font-semibold py-3 px-8 rounded-lg transition-all shadow-md hover:shadow-lg">
              立即创建 {article.category}
              <ArrowRight className="w-4 h-4" />
            </button>
          </Link>
        </div>
      </article>
    </div>
  )
}

说白了,这个就是生成画图教程的。


下面是具体的解释:

  • 动态内容渲染 (WikiPage)

    • 接收 URL 中的 slug 参数。
    • 调用 getWikiBySlug(slug) 获取文章数据。
    • 如果你访问的页面不存在,它会直接跳转 404 页面 (notFound())。
  • Markdown 转换

    • 使用 ReactMarkdown 组件将存储在后台的 Markdown 文本转换为网页上的富文本(标题、段落、列表等)。
  • SEO 深度优化

    • Meta 标签 (generateMetadata) :自动生成浏览器标题栏的 Title 和 Description,以及社交媒体分享卡片(OpenGraph)。
    • 结构化数据 (JSON-LD) :向搜索引擎注入 "Article" 类型的结构化数据,帮助 Google、百度或必应更好地理解这是一篇文章,提升搜索排名。
    • 静态生成 (generateStaticParams) :在构建时(Build time)就生成好所有文章的 HTML,加载速度极快(SSG)。
  • 用户转化


同样的,solutions 也是这样的作用,就不过多赘述。


除了这些,如果用户访问一个不存在的页面,这时候我们就要定义一个 404 页面

来做一个友好的提示,并在页面中指引用户回到正确的页面。

import Link from "next/link"
import { Button } from "antd"
import { Home, Search, FileQuestion } from "lucide-react"

export default function NotFound() {
  return (
    <div className="min-h-screen bg-slate-50 flex flex-col items-center justify-center px-4">
      <div className="text-center max-w-xl">
        <div className="mb-8 flex justify-center">
          <div className="w-24 h-24 bg-blue-100 rounded-full flex items-center justify-center text-blue-600">
            <FileQuestion size={48} />
          </div>
        </div>
        <h1 className="text-4xl font-extrabold text-slate-900 mb-4">
          页面未找到 (404)
        </h1>
        <p className="text-lg text-slate-600 mb-8">
          抱歉,您访问的页面似乎迷路了。可能它被移动了,或者从未存在过。
        </p>

        <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 mb-12">
          <Link href="/">
            <Button type="primary" size="large" icon={<Home size={18} />} className="w-full h-12">
              返回首页
            </Button>
          </Link>
          <Link href="/solutions/uml-diagram">
            <Button size="large" icon={<Search size={18} />} className="w-full h-12">
              浏览绘图工具
            </Button>
          </Link>
        </div>

        <div className="border-t border-gray-200 pt-8">
          <p className="text-sm text-slate-500 mb-4">您可能在寻找:</p>
          <div className="flex flex-wrap justify-center gap-3">
            <Link href="/solutions/uml-diagram" className="px-4 py-2 bg-white border border-gray-200 rounded-full text-slate-600 hover:text-blue-600 hover:border-blue-600 transition-colors text-sm">
              UML 类图
            </Link>
            <Link href="/solutions/flowchart" className="px-4 py-2 bg-white border border-gray-200 rounded-full text-slate-600 hover:text-blue-600 hover:border-blue-600 transition-colors text-sm">
              流程图
            </Link>
            <Link href="/wiki/what-is-uml-diagram" className="px-4 py-2 bg-white border border-gray-200 rounded-full text-slate-600 hover:text-blue-600 hover:border-blue-600 transition-colors text-sm">
              什么是 UML?
            </Link>
          </div>
        </div>
      </div>
    </div>
  )
}

这些是通用的优化技巧。


那么针对于我的 IntelliDraw 画图平台,它有模板库功能啊

如果用户想搜索模板的话,是不是也需要对原始的模板页面做一下优化呢?

image.png

这是模板库页面。点击每一个模板可以查看详情。

这里注意了,项目本来的点击具体的模板逻辑是这样的:
用户点击卡片,只是放大了图表的预览。
那如果搜索引擎不去执行 JS 的话,模板详情是获取不到的啊。

所以我的优化思路是这样的:
把模板详情做成一个通用的组件,根据路由 ID 去在服务端渲染模板详情(尤其是元信息)。

这样搜索引擎访问模板页面的时候,比如:localhost/template/123
经过服务端渲染,返回的 HTML 中就包含了完整的模板信息,
增大了搜索引擎收录站点的概率

image.png

总之,SEO 优化的方式有很多,也不一定全是用我的方法,要根据自己网站的情况而来。


那么总结一下:网站的 SEO 优化方案

SEO 优化核心方案概览

  • 技术 SEO:确保搜索引擎能够顺利抓取并索引网页。
  • 关键词研究:锁定高价值、高意图的搜索词。
  • 内容营销:创造满足用户需求的优质内容。
  • 页面内优化(On-Page) :提升单个页面的相关性。
  • 外部链接(Off-Page) :建立权威度与信任感。

一、技术 SEO(底座)

在内容被看到之前,网站必须对爬虫友好。

  • 网站速度:优化图片大小、开启缓存、使用 CDN,确保移动端加载速度。
  • 移动端适配:采用响应式设计(Responsive Design)。
  • HTTPS 安全:确保安装 SSL 证书,这是谷歌等搜索引擎的排名因素。
  • 结构化数据(Schema Markup) :添加代码片段帮助搜索结果显示丰富摘要(星级、价格、FAQ)。
  • 站点地图与 Robots:提交 XML Sitemap,合理配置 robots.txt 引导抓取。

二、关键词策略(方向)

  • 建立关键词矩阵

    • 核心词:竞争大,代表行业(如“SEO 工具”)。
    • 长尾词:竞争小,转化高(如“2026 年适合初学者的免费 SEO 工具有哪些”)。
  • 意图分析:区分信息型(想了解)、导航型(找特定网站)和交易型(想购买)词汇,针对性布局内容。


三、页面内优化(细节)

  • 标题标签(Title Tags) :包含关键词,长度控制在 60 个字符以内。
  • 描述标签(Meta Descriptions) :撰写具有吸引力的文案,提高点击率(CTR)。
  • URL 结构:保持简短、清晰且包含关键词。
  • H1-H3 标签:合理使用层级标签,让内容结构化。
  • 内链布局:通过内部链接权重传递,延长用户停留时间。

四、内容与外链(权重)

  • E-E-A-T 原则:强调经验(Experience)、专业性(Expertise)、权威性(Authoritativeness)和信任感(Trustworthiness)。
  • 高质量外链:通过客座投稿、行业媒体报道获得高质量的指向链接,而非垃圾群发。
  • 定期更新:搜索引擎偏好活跃且不断完善的站点。

五、数据监测与迭代

  • Google Search Console:监控抓取错误、关键词排名及点击量。
  • Google Analytics 4 (GA4) :分析用户行为、跳出率及转化路径。
  • 竞品分析:利用 Ahrefs 或 Semrush 定期监测对手的关键词变动。

最后提醒:
SEO 优化方式多样,不一定全用本文方法,要根据自己网站的情况而定

你的网站值得被看见。
别让它,因为“没人知道”而默默下线。

欢迎体验 IntelliDraw 画图平台 —— 一个正在努力被更多人发现的 AI 绘图工具。