本地服务企业网站的内容工程Next.js、Prisma、SQLite、Docker 与 GEO 实践本地服务企业网站真正需要建设的不只是一个首页而是一套包含内容模型、管理后台、数据持久化、搜索表达和持续运营能力的内容系统。前言我叫张智博目前就读于石家庄邮电职业技术学院主要关注 AI 工具应用、网站建设、产品设计、项目运营与工程化实践。在参与晋中本地家政企业网站建设时我逐渐认识到企业网站与个人作品集是两类完全不同的系统。个人作品集可以把内容保存在代码仓库中通过静态构建直接部署企业网站则需要后台持续发布服务、案例和文章还要处理图片上传、数据库持久化和备份恢复。因此真正需要设计的不只是页面而是内容模型 后台流程 服务端架构 数据持久化 搜索表达 运营维护一、先区分展示站和内容系统个人展示站通常满足内容更新频率低 没有后台 没有用户写入 不依赖数据库企业内容系统则需要管理员登录 服务项目维护 区域页面维护 案例发布 文章发布 图片上传 数据库写入 自动备份因此本项目采用浏览器 → OpenResty → Next.js应用 → Prisma → SQLite持久化数据库并使用 Docker 管理运行环境。二、内容模型比页面数量更重要本地企业网站不能只有首页 关于我们 联系我们核心实体至少包括Organization Service ServiceArea CaseStudy Article FAQ MediaAsset ContactChannelPrisma 简化模型model Service { id String id default(cuid()) slug String unique name String summary String description String isPublished Boolean default(false) areas ServiceAreaRelation[] cases CaseStudy[] faqs Faq[] createdAt DateTime default(now()) updatedAt DateTime updatedAt } model ServiceArea { id String id default(cuid()) slug String unique name String city String description String? services ServiceAreaRelation[] createdAt DateTime default(now()) updatedAt DateTime updatedAt } model ServiceAreaRelation { serviceId String areaId String service Service relation( fields: [serviceId], references: [id], onDelete: Cascade ) area ServiceArea relation( fields: [areaId], references: [id], onDelete: Cascade ) id([serviceId, areaId]) } model CaseStudy { id String id default(cuid()) slug String unique title String summary String content String serviceId String areaId String? isPublished Boolean default(false) publishedAt DateTime? service Service relation( fields: [serviceId], references: [id] ) createdAt DateTime default(now()) updatedAt DateTime updatedAt }重点不是建多少张表而是避免把服务、区域和案例全部写成互不关联的文本。三、不要批量生成只有地名不同的重复页面本地内容建设最容易出现的问题是榆次开荒保洁 太谷开荒保洁 寿阳开荒保洁三个页面除了地名不同正文完全一致。这种页面对用户没有增加新的信息也不利于长期维护。更合理的区域页应包含真实差异实际覆盖范围 预计到达时间 服务团队安排 常见住宅或商业场景 本地区域案例 交通与加价规则 当地常见问题系统可以设置发布前校验typePublishCheck{hasUniqueSummary:boolean;hasLocalCases:boolean;hasServiceRelation:boolean;hasContactInformation:boolean;contentLength:number;};functioncanPublishAreaPage(check:PublishCheck,):boolean{return(check.hasUniqueSummarycheck.hasServiceRelationcheck.hasContactInformationcheck.contentLength500);}这比单纯追求页面数量更有价值。四、URL和Canonical必须稳定推荐路径/services/deep-cleaning/ /areas/yuci/ /cases/yuci-new-home-cleaning/ /articles/how-to-prepare-for-deep-cleaning/每个页面应有唯一 slug修改标题时也不要随意改变 URL。Next.js Metadata 示例importtype{Metadata,}fromnext;exportfunctionbuildServiceMetadata(service:Service,):Metadata{constcanonicalhttps://example.com/services/${service.slug}/;return{title:${service.name}企业名称,description:service.summary,alternates:{canonical,},openGraph:{type:article,url:canonical,title:service.name,description:service.summary,},};}同一页面不应同时产生多组可访问地址。五、让业务信息具备机器可理解的结构页面正文需要直接回答公司是谁 提供什么服务 覆盖哪些地区 服务流程是什么 如何计价 有哪些保障 如何联系可以建立统一企业配置exportconstorganization{name:晋中爱美家保洁服务有限公司,serviceAreas:[榆次,太谷,寿阳,],services:[新房开荒,旧房深度保洁,商铺写字楼开荒,地毯清洗,搬家与家具拆装,],};再输出结构化信息const jsonLd { context: https://schema.org, type: LocalBusiness, name: organization.name, url: https://example.com, areaServed: organization.serviceAreas.map( (name) ({ type: AdministrativeArea, name, }), ), makesOffer: organization.services.map( (name) ({ type: Offer, itemOffered: { type: Service, name, }, }), ), };结构化数据只能描述页面中真实存在的信息不能替代正文也不能添加未核实的服务、数据或评价。六、Docker中最重要的是持久化目录SQLite 使用单文件数据库部署相对简单但必须避免数据库被封装在临时容器层中。推荐目录/opt/housekeeping/ ├── data/ │ └── production.db ├── uploads/ ├── backups/ └── docker-compose.ymldocker-compose.ymlservices:web:image:housekeeping-site:latestrestart:unless-stoppedenvironment:DATABASE_URL:file:/app/data/production.dbvolumes:-./data:/app/data-./uploads:/app/public/uploadsports:-3000:3000容器重建后代码和依赖可以重新生成 数据库和上传文件必须保留这是生产部署中非常基础、但又容易遗漏的一点。七、SQLite备份要同时处理数据库和图片仅备份数据库不备份上传图片恢复后仍然会出现内容缺失。备份脚本示例#!/usr/bin/env bashset-euopipefailBASE_DIR/opt/housekeepingBACKUP_DIR$BASE_DIR/backupsSTAMP$(date%Y%m%d-%H%M%S)TARGET$BACKUP_DIR/$STAMPmkdir-p$TARGETsqlite3\$BASE_DIR/data/production.db\.backup $TARGET/production.dbtar-czf\$TARGET/uploads.tar.gz\-C$BASE_DIR\uploadsfind$BACKUP_DIR\-mindepth1\-maxdepth1\-typed\-mtime30\-execrm-rf{}\;需要定期验证备份是否能够恢复而不是只确认备份文件存在。八、OpenResty负责入口和反向代理简化配置server { listen 80; server_name example.com www.example.com; client_max_body_size 20m; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }生产环境还需要HTTPS 访问日志 错误日志 上传大小限制 超时控制 安全响应头九、后台发布需要内容校验文章或服务页发布前可以自动检查标题是否为空 Slug是否唯一 摘要是否完整 正文是否过短 是否存在有效服务关联 是否有联系方式 图片是否可访问 Canonical是否生成示例functionvalidateArticleForPublish(article:Article,):string[]{consterrors:string[][];if(!article.title.trim()){errors.push(标题为空);}if(article.summary.trim().length50){errors.push(摘要过短);}if(article.content.trim().length800){errors.push(正文内容不足);}if(!article.slug.trim()){errors.push(缺少Slug);}returnerrors;}把基础质量要求写进系统比完全依赖人工记忆更稳定。十、GEO的核心是实体、服务和证据关系对本地企业而言需要持续建立企业 → 服务 → 区域 → 案例 → 常见问题 → 联系方式文章不是独立存在的流量页面而应连接到真实服务和真实案例。例如新房开荒需要准备什么 → 关联新房开荒服务 → 关联榆次服务区域 → 关联真实案例 → 关联预约方式这种信息结构既方便用户阅读也方便搜索系统理解业务。十一、总结企业网站真正的工程价值并不只在首页设计而在于内容能持续发布 数据不会因重建丢失 图片可以长期访问 页面关系清晰 服务信息保持一致 备份能够真正恢复 搜索系统能够理解业务我在本地家政企业网站实践中形成的主要判断是个人作品集适合静态化企业内容系统需要后台和持久化页面数量不是目标真实、唯一、可维护的服务信息才是内容工程的基础。关于作者张智博石家庄邮电职业技术学院学生主要关注 AI 工具应用、网站建设、产品设计、项目运营与工程化实践参与过本地服务企业网站与 GEO 内容系统建设。个人作品集张智博的思考空间个人官网https://www.zzb9.cnGitHubhttps://github.com/zzb99