从“无人问津”到“被搜索引擎发现”:一个程序员的 SEO 实战指南
引子:一个令人惋惜的故事
你是一个小阿巴,好不容易开发好了一个网站,这时候你满怀欣喜地将它上线。
这时候的你,时不时打开你的网站后台,去查看有没有新注册的用户。
你却发现:我去,怎么还没有人注册啊。
这时候的你再也没有了对网站运营下去的动力,于是狠心点击了阿里云服务器强制关机按钮——
从此,一代优秀的产品就此下线。
正文开始:为什么你的网站没人用?
首先我们来想一想,我们费劲开发的网站为什么没有人使用?
是产品不够好吗?还是 idea 不行?
肯定和这两个有一定的关系。
但是,我觉得最重要的是:得有人知道你的网站/产品啊!
再好的产品,没有人知道,没有人使用,到最后还是要下线.
我相信这也是很多个人开发者所面临的问题吧。
并且随着 AI 的发展,AI 现在这么火热,个人开发者也是越来越多了。
网站没人用导致没有运营的成本,就会增大网站下线的风险。
有人觉得产品本身的质量也会有影响,但我觉得哈——
直接说最现实的问题:能不能变现!!
有人用才有的赚。
那么怎么有人用呢?
当然,可以投广告、写优质内容什么的。
但是这些对于程序员来说好像都不熟悉。
其实还有一个方式就是:做网站的 SEO 优化。
什么是 SEO?
我们来看解释:
SEO 是一种通过优化网站内容和技术结构,提高网站在搜索引擎(如 Google、百度、Bing)自然排名中的位置,从而获得更多免费流量(自然流量)的技术和过程。
众所周知,搜索引擎,包括百度、必应、谷歌还有一些其他的,都是通过网络爬虫技术实现的,说白了就是网页抓取。
当我们访问一个网站的时候,在浏览器输入网址 baidu.com,
百度的服务器就会返回 HTML 页面,然后浏览器再去解析 HTML 代码,就变成了我们所看到的网页。
那么 HTML 中包含什么东西呢?
在我们做前端开发的时候,比如使用 React,你在网站的顶部导航栏写了一个本项目的 GitHub 地址,这个不就是一个 URL 吗?
所以 HTML 页面中包含很多的 URL。
网络爬虫程序就像一个蜘蛛一样,在互联网这张类似于大蜘蛛网上爬行:
每访问到一个网站,它都会提取出页面的 URL,再去访问这些提取到的 URL,以此类推,
就像函数的递归调用一样,同时还会将这个网址收入到搜索引擎自己的数据库中去。
当然这只是一个简单的概述。
搜索引擎抓取前的关键文件
那具体会怎么做呢?
搜索引擎会先访问该网站的 robots.txt 文件,
文件内容是告诉搜索引擎,哪些资源可以访问,哪些资源不可以访问。
同时站长通常会在网站放一个 Sitemap(站点地图) 。
所谓站点地图,就是一个文件,里面包含了网页、视频、图片或其他文件有关的信息。
站点地图可以帮助搜索引擎发现、爬取和索引网站的所有内容,
它还可以告诉搜索引擎网站上的页面什么时候更新,以及哪些网页和文件比较重要。
Sitemap 最常说的有两种类型:
- 一个是给用户看的(HTML 站点地图)
- 一个是给搜索引擎看的(XML 站点地图)
这里我们主要说的是 XML 站点地图。
有些站点地图是静态的直接写死的,但是大多网站的站点地图是动态生成的.
比如我开发的 IntelliDraw 画图平台,这里的站点地图就是动态生成的:
就拿我这个项目来说,因为我是使用 Next.js 服务端渲染技术实现的,天生就对 SEO 比较友好,尤其是 Next.js 的 MetaData API。
这里的 robots.txt 文件也是动态生成的:
那么,我们自己该如何去优化一个网站的 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 文件
它主要做了以下三件事:
- 定义固定页面:手动列出了首页、关于我们、解决方案等固定的基础路径。
- 获取动态页面:从后端 API 抓取最新的 100 个“模板”详情页链接,并设定了 1 小时更新一次 的缓存策略,避免频繁请求服务器。
- 合并输出:将固定页面和动态抓取的页面合并在一起,告诉搜索引擎(如 Google、百度)你网站上有哪些页面需要被收录。
还有 Static Routes 部分
Static Routes 的作用就是查漏补缺:
- 补充数据库盲区:API 只能告诉你有哪些模板 ID,但它不知道你还有
/about、/solutions/flowchart这些固定页面。如果你不手动写上,搜索引擎可能就不会优先爬取这些核心页面。 - 定义核心权重:这些静态页面通常是网站的入口页或营销页(如首页、产品介绍),对 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 动态路由:
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())。
- 接收 URL 中的
-
Markdown 转换:
- 使用
ReactMarkdown组件将存储在后台的 Markdown 文本转换为网页上的富文本(标题、段落、列表等)。
- 使用
-
SEO 深度优化:
- Meta 标签 (
generateMetadata) :自动生成浏览器标题栏的 Title 和 Description,以及社交媒体分享卡片(OpenGraph)。 - 结构化数据 (
JSON-LD) :向搜索引擎注入 "Article" 类型的结构化数据,帮助 Google、百度或必应更好地理解这是一篇文章,提升搜索排名。 - 静态生成 (
generateStaticParams) :在构建时(Build time)就生成好所有文章的 HTML,加载速度极快(SSG)。
- Meta 标签 (
-
用户转化:
- 文章底部包含一个引导模块,鼓励读完文章的用户直接去使用 IntelliDraw 画图平台。
同样的,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 画图平台,它有模板库功能啊
如果用户想搜索模板的话,是不是也需要对原始的模板页面做一下优化呢?
这是模板库页面。点击每一个模板可以查看详情。
这里注意了,项目本来的点击具体的模板逻辑是这样的:
用户点击卡片,只是放大了图表的预览。
那如果搜索引擎不去执行 JS 的话,模板详情是获取不到的啊。
所以我的优化思路是这样的:
把模板详情做成一个通用的组件,根据路由 ID 去在服务端渲染模板详情(尤其是元信息)。
这样搜索引擎访问模板页面的时候,比如:localhost/template/123,
经过服务端渲染,返回的 HTML 中就包含了完整的模板信息,
增大了搜索引擎收录站点的概率。
总之,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 绘图工具。