拓冰建站拓冰建站
首页 / 资讯中心 / 正文

基于Nuxt3+Vue3+TS的仿Airbnb项目实战解析

简介一份基于Vite、Vue3、TypeScript与Nuxt3整合开发的仿爱彼迎网站源码包定位为前端进阶学习项目适合已经掌握Vue基础、希望了解现代工程化与SSR实践的开发者。资源共42个文件主要包括12个TypeScript文件用于类型定义与业务逻辑8个SCSS样式文件实现响应式与主题定制4个Vue组件文件承载页面级和通用组件另有JSON配置与静态资源配套支撑整体压缩包仅302KB结构紧凑、易于阅读和二次开发。目前已有1407人学习了这套源码口碑积累说明其具备参考价值。压缩包内包含完整的Nuxt项目目录如pages、components、layouts、api、router、utils等模块读者可以从中学习Vite构建配置、Nuxt3路由和数据获取机制、Composition API封装思路以及TypeScript在真实项目中的类型约束方法还能借鉴爱彼迎风格界面的布局拆分与样式组织策略是系统梳理Vue全家桶和SSR技术栈的实用参考资料。1. Nuxt3 Vue3 TS 仿Airbnb项目先讲清楚一件事看到“基于ViteVue3TSNuxt3的仿爱彼迎网站源码.zip”这个标题我第一反应不是“源码能跑”而是这四个技术名词为什么会被放到同一个项目里。Vite 是 Nuxt3 的默认构建引擎Vue3 是 Nuxt3 的组件基础TSTypeScript提供编译期类型约束Nuxt3 则把服务端渲染、路由、数据请求这些偏“工程化”的能力一次性补齐。仿Airbnb这种业务既有强交互的筛选列表、地图定位又有需要被搜索引擎收录的房源详情页选这套栈确实比纯 SPA 更合适。本文不评价这份源码本身的代码质量而是顺着标题讲清楚一个新团队拿到这个项目应该从哪些地方下手改。2. 从零搭建 Vite Vue3 TS 的 Nuxt3 工程先别急着看业务代码拿到一个“Nuxt3项目”建议先确认工具链版本和目录结构。标题里同时出现 Vite、Vue3、Nuxt3常见做法是直接创建 Nuxt3 项目因为 Vite 和 Vue3 都已经内置不需要手动拼装。2.1 创建 Nuxt3 项目Vite 作为内置构建工具用终端执行npx nuxilatest init airbnb-like cd airbnb-like npm install npm run devNuxt3 项目初始化后nuxi会自动生成vite.config.ts可覆盖配置nuxt.config.ts负责应用级配置app.vue是入口组件pages/目录即路由。Vite 在这里不是独立配置项而是 Nuxt3 默认的打包器所以不需要再执行npm create vite。这里有一个容易踩的坑某些教程会让你手动装vite和vitejs/plugin-vue但 Nuxt3 项目里这两个包是隐性依赖手动安装反而可能造成版本冲突。验证 Vite 是否正常工作直接看npm run dev启动速度正常应该在几百毫秒内完成预构建。2.2 vue3 ts 环境配置TS 编译与路径别名Nuxt3 对 TS 的支持是开箱即用的但层面和普通 vue3 项目不同。它的类型系统覆盖了三块组件内部变量、nuxt.config配置项、以及useFetch等自动导入 API 的返回值。为了让编辑器识别路径别名需要在tsconfig.json里显式声明{ extends: ./.nuxt/tsconfig.json, compilerOptions: { paths: { ~/*: [./*], /*: [./*] } } }更多 vue3 环境配置这块常被忽略的是~和在 Nuxt3 中都指向项目根目录这和 Vite 项目的默认习惯一致但如果团队混用过 Vite 脚手架项目可能误以为指向src目录。别改这个约定否则你会面临 TS 路径提示和 Vite 解析不一致的问题。技术上有一个关键点Nuxt3 的tsconfig.json会由构建过程动态生成/.nuxt/tsconfig.json所以不要手动往include里塞文件路径覆盖compilerOptions就足够了。2.3 Vite 配置代理与 env 变量仿Airbnb网站必然要请求房源数据开发环境下跨域问题用 Vite 代理解决。在nuxt.config.ts里这样配export default defineNuxtConfig({ vite: { server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true, rewrite: path path.replace(/^\/api/, ) } } } }, runtimeConfig: { public: { apiBase: /api } } })这段配置的逻辑所有/api开头的请求在开发环境转发到后端接口服务runtimeConfig.public里的apiBase在服务端和客户端都会暴露组件里用useRuntimeConfig()读取。如果你是 vue3 学习阶段转过来的可以把runtimeConfig理解为升级版的import.meta.env.VITE_API_BASE它同时覆盖了 SSR 环境下的取值问题。这里有个细节区分import.meta.env是 Vite 提供的变量编译时静态替换runtimeConfig是 Nuxt 提供的运行时配置适合部署后不重新构建就修改的场景。两类变量可能同时出现在项目里看到代码里混用不要惊讶。3. 用 Vue3 组合式 API 拆解仿Airbnb核心搜索组件仿Airbnb的业务核心是“筛选条件 → 房源列表 → 详情跳转”这个链路用 Vue3 组合式 API 写非常顺手。重点不在于一个个组件而在于状态应该放在哪个作用域。3.1 useState 管理跨页面筛选条件如果筛选条件只放在组件局部用户切到详情页再返回时数据就丢了。Nuxt3 提供了useState它是跨组件、跨请求共享的响应式状态机制// composables/useFilter.ts export const useFilter () { const filters useState(list-filter, () ({ city: 杭州, checkIn: , checkOut: , guests: 1, priceRange: [0, 2000], })) const setCity (city: string) { if (filters.value.city city) return filters.value.city city } return { filters, setCity } }注意useState第一个参数list-filter是全局唯一 key组件里调用useFilter()时拿到的是同一份数据。它不是ref的简单替代而是 Nuxt3 在服务端渲染和客户端渲染之间维持状态一致的桥梁。如果这里用了全局ref会出现 SSR 阶段每个请求共享同一份数据的问题最直观的故障表现是用户A搜索“上海”用户B刷新后看到的也是“上海”。3.2 仿Airbnb房源卡片 Grid 组件组件的部分有一个值得对照 Vue2 的点是事件写法。Vue3 中$emit需要显式声明template div classgrid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 HouseCard v-forhouse in filteredHouses :keyhouse.id :househouse selecthandleSelect / /div /template script setup langts import type { House, HouseFilter } from ~/types/api const props defineProps{ houses: House[] filter: HouseFilter }() const emit defineEmits{ (e: select, houseId: string): void }() const filteredHouses computedHouse[](() props.houses.filter((h) { return h.price props.filter.priceRange[0] h.price props.filter.priceRange[1] })) const handleSelect (houseId: string) { emit(select, houseId) } /script关于 vue3 computed 的使用computed的返回值在模板里是只读的尽量不要尝试在script setup里修改它的.value。如果你看到项目代码里filteredHouses.value.push(...)那基本是个 bug正确的是把 push 操作放到源数据houses上。3.3 房源列表 v-for 的 key 与性能渲染几百条房源数据时v-for的 key 必须用房源 id不能用数组下标。原因很简单筛选条件变化时Vue3 的虚拟 DOM diff 通过 key 判断节点是否复用。用下标会导致列表项的状态错位尤其当卡片里嵌入了收藏按钮这类交互组件时用户会看到收藏状态发生张冠李戴。如果对 vue2 和 vue3 区别感兴趣可以在仿Airbnb场景里对比一下vue2 对于此类列表需要计算属性配合Object.freeze优化vue3 直接依赖 Proxy 实现响应式数据量在 500 条以内时不需要额外优化。超过 1000 条再考虑shallowRef或虚拟滚动不要为了炫技提前引入复杂方案。4. Nuxt3 数据获取与路由仿Airbnb的房源接口接入Nuxt3 的数据请求方案和 vue3 项目最大的不同是拥有了useFetch、useAsyncData这两个自动导入的请求函数它们和 Nuxt3 路由深度绑定。4.1 useFetch 请求房源列表处理好 SSR 和 CSR 的边界仿Airbnb的列表页标准写法是在pages/index.vue里script setup langts interface ListResponse { data: House[] total: number } const { data, pending, error, refresh } await useFetchListResponse(/api/houses, { params: { city: 杭州, page: 1 }, server: true }) /scriptuseFetch是 Nuxt3 对 Vue3 组合式 API 的扩展它在服务端执行一次请求序列化数据后随 HTML 输出到客户端。客户端激活时不会再发重复请求而是直接使用服务端payload里的数据。这个行为由server: true控制这个参数在纯 vue3 项目里没有对应物。await 在顶层使用是有讲究的script setup顶层 await 会把整个组件标记为 async 依赖跳转路由时等待数据返回才渲染。对仿Airbnb这种首屏依赖列表数据的页面这是合理选择但详情页的非关键数据谨慎使用顶层 await会导致白屏体验。4.2 动态路由与路由参数校验房源详情页的路由是/house/[id].vue在pages/house/[id].vue中script setup langts const route useRoute() const id Array.isArray(route.params.id) ? route.params.id[0] : route.params.id const { data: detail } await useFetchHouseDetail(/api/houses/${id}, { key: house-detail-${id}, server: true }) if (!detail.value) { throw createError({ statusCode: 404, statusMessage: 房源不存在或已下架 }) } /script这里的key参数很重要。Nuxt3 的useFetch内部基于useAsyncData默认 key 是由请求 URL 和参数生成的哈希。如果你在多个组件里请求同一个 URL 但希望拿到不同处理逻辑的数据不显式传key会互相覆盖缓存。我一般习惯把业务 id 拼进 key至少能保证路由跳转时数据不串。关于 ts 泛型应用这里的useFetchHouseDetail声明了返回值类型模板里detail.value.title会自动获得类型推导。如果项目里HouseDetail接口定义了price是number而接口返回了字符串1200编译阶段不会报错但运行时渲染¥ 1200 晚会变成字符串拼接排查成本很高。建议对接口返回加一层运行时校验或者用 zod 配合 TS 泛型做 schema 校验。4.3 服务端渲染模式下请求地址的坑在 SSR 请求中后端有时候会校验Referer或要求绝对路径。如果你直接用/api/houses这种相对地址Node.js 环境下的 fetch 会以localhost:3000为基础拼接这在开发环境没问题部署到线上却可能请求到内网或错误的域名。处理方案是把运行时配置里的apiBase定位一个绝对地址// nuxt.config.ts runtimeConfig: { apiBase: process.env.API_BASE_URL || http://internal-upstream:8080 }然后在请求函数中拼接const config useRuntimeConfig() const { data } await useFetch(${config.apiBase}/houses)这个问题的症状很迷惑页面能显示但接口响应时间超长或者本地跑得好好的部署后列表一直是空数组。排查顺序是先看page source的 HTML 里的__NUXT_DATA__里有没有渲染数据。如果 SSR 请求失败了这个 payload 里对应字段不会是[]就是null。5. 构建产物分析、nginx 部署与 vue3 项目上线踩坑点仿Airbnb这类前端项目上线构建产物体积和部署配置决定了用户体验的下限。尤其是用了 Nuxt3 这种同构框架和普通 vue3 商城项目相比它的构建输出多了.output/server目录部署方式彻底不同。5.1 最小化验证构建过程先执行构建命令npm run build正常情况下Nuxt3 会输出.nuxt/目录用于本地缓存和.output/目录生产产物。.output/public是静态资源.output/server/index.mjs是 Node 服务端入口。一个实用的检查命令看构建产物里有没有被意外塞进本地开发文件find .output -name *.map | wc -l如果 sourcemap 文件个数很多说明nuxt.config.ts里sourcemap配置误开了生产环境选项。正确做法export default defineNuxtConfig({ sourcemap: process.env.NODE_ENV ! production, })5.2 用 nginx 部署 Vue3 项目的三种姿势仿Airbnb项目的部署不像纯 vue3 项目那样只有一个dist。分情况讨论场景一纯静态部署SSGnuxt generate生成dist/目录nginx 直接指向静态文件server { listen 80; server_name example.com; root /var/www/airbnb-like/dist; index index.html; location / { try_files $uri $uri/ /index.html; } }场景二Node 服务部署SSR安装 PM2 把.output/server/index.mjs跑起来nginx 反代server { listen 80; server_name example.com; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }场景三动态 API 与页面同域如果后端接口是/api路径需要在 nginx 里分流而不是在 Nuxt 服务端再套一层代理否则会造成请求链路过长server { listen 80; location /api/ { proxy_pass http://127.0.0.1:8080; } location / { proxy_pass http://127.0.0.1:3000; } }5.3 响应头与缓存策略仿Airbnb网站的图片资源量大建议在 nginx 里对/images/目录配置强缓存location ~* /images/.*\.(webp|jpg|jpeg)$ { expires 30d; add_header Cache-Control public, immutable; }对 HTML 页面本身必须设置no-cache否则 SSR 生成出来的页面会被 CDN 缓存用户看不到最新的房源价格location / { add_header Cache-Control no-cache; }关于 vue3 项目在 edge 浏览器上有时候无法关闭浏览器右上角的最小化按钮这个热词它其实是个页面全屏 API 误用的坑和 nginx 无关。仿Airbnb项目中如果嵌入了地图的requestFullscreen调用建议监听fullscreenchange事件在组件卸载时退出全屏否则 Edge 浏览器的用户会遇到界面异常。6. 进阶技巧用 TS 泛型统一仿Airbnb的 API 响应结构减少重复定义最后一个话题回到 TypeScript 本身。仿Airbnb项目里接口数量一多你会看到组件里写了这样的接口定义interface House { id: number title: string price: number rating: number }列表接口返回{ data: House[], total: 100 }详情接口返回{ data: HouseDetail }如果每个页面都重复定义data和total这两个字段接口字段一改全局替换成本很高。用 TS 泛型做一个统一包装是常见解法declare namespace Api { interface ResultT { code: number message: string data: T } } export type HouseListResult Api.ResultHouse[] export type HouseDetailResult Api.ResultHouse然后在请求中const { data } await useFetchApi.ResultHouseListResponse[](/api/houses)这样可以做到一处定义多处使用。更进一步用infer从请求函数中反向推断类型可以让列表筛选函数的参数类型和接口字段类型同步变更type UnwrapResultT T extends Api.Resultinfer D ? D : never type HouseListData UnwrapResultApi.ResultHouse[]这里UnwrapResult把ResultHouse[]里的House[]抽出来当接口字段变化时用法侧的类型会自动跟着变不必手动修改每个组件的 props 类型。实际上在大团队里这就是避免 vue3 ts 报错最有效的手段——大多数 TS 编译错误都源于手写的接口字段和后端实际返回不一致。另外一个值得试的技巧是ts namespace的使用。Nuxt3 项目里会自动导入模块的类型可以用declare namespace集中管理所有后端返回的枚举值。比如房源的status字段用字符串字面量类型约束配合 IDE 提示能省掉大量不必要的运行时判断export type HouseStatus on_sale | pending | deleted写代码的时候如果看到项目里大量出现ts-ignore不要急着移除先看是不是因为后端返回了联合类型。这一层校验建议放在 API 层统一完成页面组件里不再处理异常格式这也是仿Airbnb业务从 Demo 走向生产的关键一步。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门