Nuxt 3开发企业级SSR应用的实践指南

大家好,我是一名前端开发者,专注于Vue生态和前端技术的分享!继上一篇文章带大家打造Vue 3组件库后,今天我们将深入探索Nuxt
3,开发一个企业级SSR(服务端渲染)应用。本文以一个博客系统为例,涵盖Nuxt
3项目搭建、Pinia状态管理、数据预取、性能优化和部署全流程,助你在企业项目中快速落地SSR!喜欢这篇内容?欢迎关注我的博客和公众号(微信搜索:小贺前端笔记),更多干货和实战项目等你来发现!

为什么选择Nuxt 3?

Nuxt 3是基于Vue 3的强大框架,特别适合企业级SSR应用:

  • SEO友好:服务端渲染确保搜索引擎能抓取动态内容。
  • 首屏性能:预渲染和数据预取提升用户体验。
  • 开箱即用:内置路由、状态管理和构建工具,减少配置。
  • 生态丰富:支持Pinia、VueUse、TypeScript等现代技术。

本文将以一个博客系统为例,展示如何用Nuxt 3实现企业级SSR应用。

1. 项目初始化

我们使用Nuxt 3搭建一个博客系统,支持文章列表、详情页和搜索功能。

1.1 创建项目

运行以下命令初始化Nuxt 3项目:

npx nuxi@latest init nuxt3-blog
cd nuxt3-blog
npm install

1.2 安装依赖

为支持状态管理和数据请求,安装以下依赖:

npm install @pinia/nuxt pinia @vueuse/nuxt axios
  • @pinia/nuxt:集成Pinia状态管理。
  • pinia:轻量级状态管理库。
  • @vueuse/nuxt:提供VueUse工具函数。
  • axios:用于API请求。

1.3 项目结构

创建一个清晰的目录结构:

nuxt3-blog/
├── assets/                 # 静态资源
│   └── css/
│       └── main.css
├── components/             # 组件
│   ├── ArticleCard.vue
│   └── SearchBar.vue
├── composables/            # 组合式函数
│   └── useApi.ts
├── layouts/                # 布局
│   └── default.vue
├── pages/                  # 页面
│   ├── index.vue
│   └── article/[id].vue
├── stores/                 # 状态管理
│   └── article.ts
├── public/                 # 公共资源
├── nuxt.config.ts          # Nuxt配置文件
├── package.json
└── README.md

2. 配置Nuxt 3

2.1 修改nuxt.config.ts

配置模块、CSS和TypeScript支持:

export default defineNuxtConfig({
  devtools: { enabled: true },
  modules: ['@pinia/nuxt', '@vueuse/nuxt'],
  css: ['~/assets/css/main.css'],
  typescript: {
    strict: true,
  },
});

2.2 创建全局样式

assets/css/main.css中添加基础样式:

body {
  font-family: Arial, sans-serif;
  margin: 0;
  padding: 0;
  background-color: #f5f5f5;
}

.container {
  max-width: 1200px;
  margin: 0 auto;
  padding: 20px;
}

3. 开发核心功能

我们将实现博客的文章列表、详情页和搜索功能。

3.1 数据接口

假设使用JSONPlaceholder模拟后端API:

  • 文章列表:https://jsonplaceholder.typicode.com/posts
  • 文章详情:https://jsonplaceholder.typicode.com/posts/:id

composables/useApi.ts中封装API请求:

import axios from 'axios';

export const useApi = () => {
  const baseURL = 'https://jsonplaceholder.typicode.com';

  const getPosts = async () => {
    const { data } = await axios.get(`${baseURL}/posts`);
    return data;
  };

  const getPostById = async (id: string) => {
    const { data } = await axios.get(`${baseURL}/posts/${id}`);
    return data;
  };

  return { getPosts, getPostById };
};

3.2 状态管理

stores/article.ts中使用Pinia管理文章数据:

import { defineStore } from 'pinia';

export const useArticleStore = defineStore('article', {
  state: () => ({
    posts: [] as any[],
    searchQuery: '',
  }),
  actions: {
    setPosts(posts: any[]) {
      this.posts = posts;
    },
    setSearchQuery(query: string) {
      this.searchQuery = query;
    },
  },
  getters: {
    filteredPosts: (state) => {
      if (!state.searchQuery) return state.posts;
      return state.posts.filter((post) =>
        post.title.toLowerCase().includes(state.searchQuery.toLowerCase())
      );
    },
  },
});

3.3 组件开发

3.3.1 ArticleCard.vue

components/ArticleCard.vue中创建文章卡片组件:

<template>
  <div class="article-card">
    <h3>{{ article.title }}</h3>
    <p>{{ article.body.slice(0, 100) }}...</p>
    <NuxtLink :to="`/article/${article.id}`">阅读全文</NuxtLink>
  </div>
</template>

<script setup lang="ts">
defineProps<{
  article: { id: number; title: string; body: string };
}>();
</script>

<style scoped>
.article-card {
  background: white;
  padding: 20px;
  margin-bottom: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.article-card h3 {
  margin: 0 0 10px;
}

.article-card a {
  color: #409eff;
  text-decoration: none;
}
</style>
3.3.4 SearchBar.vue

components/SearchBar.vue中创建搜索栏:

<template>
  <input
    v-model="searchQuery"
    type="text"
    placeholder="搜索文章..."
    class="search-bar"
  />
</template>

<script setup lang="ts">
import { useArticleStore } from '~/stores/article';

const store = useArticleStore();
const searchQuery = computed({
  get: () => store.searchQuery,
  set: (value) => store.setSearchQuery(value),
});
</script>

<style scoped>
.search-bar {
  width: 100%;
  padding: 10px;
  margin-bottom: 20px;
  border: 1px solid #dcdcdc;
  border-radius: 4px;
  font-size: 16px;
}
</style>

3.4 布局

layouts/default.vue中定义默认布局:

<template>
  <div>
    <header>
      <h1>Nuxt 3 博客</h1>
      <nav>
        <NuxtLink to="/">首页</NuxtLink>
      </nav>
    </header>
    <main class="container">
      <slot />
    </main>
  </div>
</template>

<style scoped>
header {
  background: #409eff;
  color: white;
  padding: 20px;
  text-align: center;
}

nav a {
  color: white;
  margin: 0 10px;
  text-decoration: none;
}

h1 {
  margin: 0;
}
</style>

3.5 页面开发

3.5.1 文章列表页(index.vue)

pages/index.vue中实现文章列表和搜索:

<template>
  <div>
    <SearchBar />
    <ArticleCard
      v-for="post in filteredPosts"
      :key="post.id"
      :article="post"
    />
  </div>
</template>

<script setup lang="ts">
import { useArticleStore } from '~/stores/article';
import { useApi } from '~/composables/useApi';

const store = useArticleStore();
const { getPosts } = useApi();

const { data: posts } = await useAsyncData('posts', async () => {
  const data = await getPosts();
  store.setPosts(data);
  return data;
});

const filteredPosts = computed(() => store.filteredPosts);
</script>
  • 使用 useAsyncData 在服务端预取文章数据。
  • 通过 Pinia 的 filteredPosts getter 实现搜索过滤。
3.5.2 文章详情页(article/[id].vue)

pages/article/[id].vue中实现文章详情:

<template>
  <div v-if="post">
    <h2>{{ post.title }}</h2>
    <p>{{ post.body }}</p>
    <NuxtLink to="/">返回首页</NuxtLink>
  </div>
  <div v-else>文章不存在</div>
</template>

<script setup lang="ts">
import { useApi } from '~/composables/useApi';

const route = useRoute();
const { getPostById } = useApi();

const { data: post } = await useAsyncData(`post-${route.params.id}`, () =>
  getPostById(route.params.id)
);
</script>

<style scoped>
h2 {
  margin-bottom: 20px;
}

p {
  line-height: 1.6;
}

a {
  color: #409eff;
  text-decoration: none;
}
</style>

4. 性能优化

4.1 懒加载组件

为大型组件启用懒加载,减少首屏加载时间:

<script setup lang="ts">
const ArticleCard = defineAsyncComponent(() =>
  import('~/components/ArticleCard.vue')
);
</script>

4.2 图片优化

nuxt.config.ts中启用图片优化(需安装@nuxt/image):

npm install -D @nuxt/image
export default defineNuxtConfig({
  modules: ['@pinia/nuxt', '@vueuse/nuxt', '@nuxt/image'],
  image: {
    provider: 'static',
  },
});

4.3 缓存API响应

composables/useApi.ts中添加缓存:

import axios from 'axios';
import { useNuxtApp } from '#app';

export const useApi = () => {
  const baseURL = 'https://jsonplaceholder.typicode.com';
  const cache = new Map();

  const getPosts = async () => {
    if (cache.has('posts')) return cache.get('posts');
    const { data } = await axios.get(`${baseURL}/posts`);
    cache.set('posts', data);
    return data;
  };

  const getPostById = async (id: string) => {
    const cacheKey = `post-${id}`;
    if (cache.has(cacheKey)) return cache.get(cacheKey);
    const { data } = await axios.get(`${baseURL}/posts/${id}`);
    cache.set(cacheKey, data);
    return data;
  };

  return { getPosts, getPostById };
};

5. 部署到Vercel

5.1 配置Vercel

  1. 安装Vercel CLI:

    npm install -g vercel
    
  2. 部署项目:

    vercel
    
  3. 配置环境变量(如果有API密钥):
    在Vercel仪表板中添加环境变量。

5.2 注意事项

  • 确保 nuxt.config.ts 中设置 ssr: true
  • 检查 package.jsonscripts 中有 "build": "nuxt build"
  • 使用 Vercel 的自动缩放功能优化性能。

6. 常见问题与解决

  • Q:首屏加载慢?
    A:使用 defineAsyncComponent 懒加载组件,或启用 @nuxt/image 优化图片。
  • Q:数据预取失败?
    A:检查 useAsyncData 的 key 是否唯一,确保 API 响应正常。
  • Q:部署后样式丢失?
    A:确认 assets/css/main.css 已正确引入,检查 Vercel 的构建日志。

7. 总结

通过以上步骤,我们用Nuxt 3开发了一个企业级SSR博客系统。核心要点包括:

  • 使用 useAsyncData 实现服务端数据预取。
  • 集成 Pinia 和 VueUse 提升开发效率。
  • 通过懒加载和缓存优化性能。
  • 部署到 Vercel 实现高可用性。

相关推荐

想深入学习Vue 3和前端开发?以下是几篇值得一读的文章:

Vue 3 中的新特性:Suspense和Teleport

从初级到高级前端:如何写出高质量代码,迈向职业新高度

从零打造一个Vue 3组件库:开发、打包与发布到NPM

你在使用Nuxt 3开发SSR应用时遇到过哪些挑战?欢迎在评论区分享你的经验!💬

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值