Vue 3 与 Nuxt 4 写法区别

57 阅读14分钟

Vue 3 与 Nuxt 4 写法区别详细分析

Vue 3 是一个渐进式 JavaScript 框架,用于构建用户界面。它带来了 Composition API、Teleport、Fragments 等新特性,并对性能进行了大幅优化。Nuxt 4 是一个基于 Vue 3 的全栈框架,它在 Vue 3 的基础上提供了构建通用应用(Universal Applications,即服务端渲染 SSR、静态站点生成 SSG 或单页应用 SPA)所需的开箱即用的功能和约定。

虽然 Nuxt 4 底层使用了 Vue 3,但由于其作为框架的定位,它在项目结构、数据获取、路由、服务端能力等方面与纯 Vue 3 应用有着显著的写法差异。以下将从多个方面详细列举并分析这些不同之处,并提供相应的代码示例。

1. 项目结构与约定

Vue 3 (Vite 或 Vue CLI 创建的项目)

Vue 3 项目的结构相对自由,通常由开发者根据项目规模和团队规范自行组织。一个典型的 Vue 3 项目(使用 Vite 创建)可能包含以下核心目录:

  • src/:源代码目录
    • assets/:存放静态资源,如图片、CSS 预处理器文件等。
    • components/:存放可复用的 Vue 组件。
    • router/:存放 Vue Router 的路由配置。
    • stores/:存放 Pinia 或 Vuex 的状态管理模块。
    • views/pages/:存放页面组件。
    • App.vue:根组件。
    • main.jsmain.ts:应用入口文件,负责 Vue 应用的创建、挂载插件和路由等。

Vue 3 提供了极大的灵活性,但这也意味着在大型项目中,需要开发者自行建立和维护一套清晰的目录结构和规范。

Nuxt 4 (约定优于配置)

Nuxt 4 强制执行一套约定俗成的目录结构,这大大简化了项目的配置和管理,尤其是在构建通用应用时。Nuxt 4 的目录结构基于 Nuxt 3,并在此基础上可能有一些优化和调整。以下是 Nuxt 4 项目的核心目录:

  • assets/:存放未编译的静态资源,如 LESS、SASS、图片、字体等。这些资源会经过 Webpack/Vite 处理。
  • components/:存放 Vue 组件。Nuxt 4 默认支持自动导入这些组件,无需手动注册。
  • composables/:存放可复用的 Composition API 函数(组合式函数)。Nuxt 4 默认支持自动导入
  • content/:(可选) 用于 Nuxt Content 模块,存放 Markdown、YAML、CSV 等内容文件,用于构建内容驱动的网站。
  • layouts/:存放应用布局组件,用于定义页面的整体结构(如头部、底部、侧边栏)。
  • middleware/:存放中间件函数,用于在页面或路由导航之前运行。
  • pages/核心目录,Nuxt 4 根据此目录下的 .vue 文件结构自动生成路由。每个 .vue 文件代表一个页面。
  • plugins/:存放需要在 Vue 根实例实例化之前运行的 JavaScript/TypeScript 插件。这些插件可以在服务端和客户端运行。
  • public/:(对应 Nuxt 2 的 static/) 存放静态文件,如 robots.txtfavicon.ico 或不需要 Webpack/Vite 处理的图片。这些文件会直接映射到应用的根路径。
  • server/:存放服务端相关的文件,如 API 路由、服务端中间件等。这是 Nuxt 4 (基于 Nitro 引擎) 的一个重要特性。
    • api/:存放 API 路由,可以直接在这里定义 RESTful API 接口。
    • middleware/:存放服务端中间件。
    • routes/:存放自定义服务端路由。
  • store/:(可选) 存放 Pinia 或 Vuex 状态管理模块。Nuxt 4 推荐使用 Pinia。
  • app.vue:Nuxt 4 应用的根组件,取代了 Nuxt 2 的 layouts/default.vuepages/index.vue 的部分功能,是应用的入口点。
  • nuxt.config.ts (或 .js):Nuxt 的配置文件,用于配置模块、插件、构建选项、运行时配置等。

写法差异总结:

  • 项目入口: Vue 3 通常是 main.js/ts,Nuxt 4 是 app.vue
  • 自动导入: Nuxt 4 默认支持 components/composables/ 目录下的文件自动导入,Vue 3 需要手动配置或使用插件。
  • 路由: Nuxt 4 实现了基于文件系统的自动路由pages/ 目录),极大地简化了路由管理。Vue 3 需要手动配置 vue-router
  • 服务端能力: Nuxt 4 内置了 server/ 目录,支持直接在项目中编写 API 路由和服务器端逻辑,这是纯 Vue 3 应用不具备的。
  • 配置文件: Nuxt 4 使用 nuxt.config.ts 进行统一配置,包括构建、模块、插件、运行时配置等。

2. 数据获取 (Data Fetching)

在通用应用中,数据获取是一个关键环节,因为数据可能需要在服务端渲染之前准备好。Vue 3 本身不提供数据获取的特定机制,而 Nuxt 4 则提供了强大的内置组合式函数。

Vue 3 (客户端渲染)

在纯客户端渲染的 Vue 3 应用中,数据通常在组件的 onMounted 生命周期钩子中通过 AJAX 请求获取。这意味着数据获取发生在浏览器端,用户可能会看到加载状态。

<!-- Vue 3 Options API -->
<template>
  <div>
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
  </div>
</template>

<script>
import { ref, onMounted } from 'vue';
import axios from 'axios';

export default {
  setup() {
    const post = ref({});

    onMounted(async () => {
      try {
        const response = await axios.get('/api/posts/1');
        post.value = response.data;
      } catch (error) {
        console.error('Error fetching post:', error);
      }
    });

    return {
      post
    };
  }
};
</script>
<!-- Vue 3 Composition API with <script setup> -->
<template>
  <div>
    <h1>{{ post.title }}</h1>
    <p>{{ post.content }}</p>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import axios from 'axios';

const post = ref({});

onMounted(async () => {
  try {
    const response = await axios.get('/api/posts/1');
    post.value = response.data;
  } catch (error) {
    console.error('Error fetching post:', error);
  }
});
</script>

Nuxt 4 (通用数据获取)

Nuxt 4 提供了 useAsyncDatauseFetch 两个强大的组合式函数,用于在服务端或客户端进行数据获取,以支持 SSR 和 SSG。它们都可以在组件的 setup 函数或 <script setup> 中使用。

  • useAsyncData:用于异步获取数据,并将数据合并到组件的响应式状态中。此方法在服务端渲染时会在服务端执行,在客户端导航时会在客户端执行。它提供了更细粒度的控制,例如手动触发、缓存控制等。

    <!-- Nuxt 4 Composition API with <script setup> -->
    <template>
      <div>
        <h1>{{ post?.title }}</h1>
        <p>{{ post?.content }}</p>
        <p v-if="pending">Loading...</p>
        <p v-if="error">Error: {{ error.message }}</p>
      </div>
    </template>
    
    <script setup>
    const { data: post, pending, error, refresh } = await useAsyncData(
      'my-post',
      () => $fetch('https://api.example.com/posts/1')
    );
    
    // 可以在需要时手动刷新数据
    // refresh();
    </script>
    
  • useFetch:是 useAsyncData 的一个便捷封装,专门用于处理 API 请求。它提供了与 useAsyncData 类似的功能,但更专注于 HTTP 请求。

    <!-- Nuxt 4 Composition API with <script setup> -->
    <template>
      <div>
        <h1>{{ product?.name }}</h1>
        <p>{{ product?.description }}</p>
        <p v-if="pending">Loading product...</p>
        <p v-if="error">Error: {{ error.message }}</p>
      </div>
    </template>
    
    <script setup>
    const { data: product, pending, error } = await useFetch('/api/products/1');
    </script>
    

写法差异总结:

  • Nuxt 4 提供了专门的组合式函数(useAsyncData, useFetch)来处理通用数据获取,确保在页面渲染前数据可用,从而实现更好的 SEO 和用户体验。Vue 3 在客户端渲染模式下通常在组件挂载后获取数据,不具备服务端预取能力。

3. 服务端渲染 (SSR) 与静态站点生成 (SSG)

这是 Vue 3 和 Nuxt 4 之间最根本的区别之一。

Vue 3

Vue 3 本身是一个客户端渲染(CSR)框架。要实现 SSR,需要手动配置 vue-server-renderer 或使用像 Vite SSR 这样的工具,并搭建 Node.js 服务器来处理渲染逻辑。这通常涉及复杂的配置和额外的开发工作,需要开发者对 SSR 的原理有深入理解。

Nuxt 4

Nuxt 4 是一个开箱即用的通用应用框架,内置了对 SSR 和 SSG 的支持。开发者无需进行复杂的配置,只需通过 nuxt.config.ts 中的 target 选项进行简单的配置即可实现。

  • SSR 模式 (target: 'server'):Nuxt 4 会在每次请求时在服务器上渲染 Vue 应用程序,并将渲染好的 HTML 发送给客户端。这对于需要实时数据和良好 SEO 的应用非常有用。
  • SSG 模式 (target: 'static'):Nuxt 4 会在构建时预渲染所有页面为静态 HTML 文件。这些文件可以直接部署到任何静态文件服务器上,提供极快的加载速度和优秀的 SEO。适用于内容不经常变化的网站,如博客、文档网站。

nuxt.config.ts 配置示例:

// nuxt.config.ts
export default defineNuxtConfig({
  ssr: true, // 启用 SSR (默认)
  // 或者
  // ssr: false, // 禁用 SSR,只进行客户端渲染 (SPA)

  // 如果要生成静态站点,可以在 build 命令中使用 `nuxt generate`
  // 或者在 nuxt.config.ts 中配置 generate 选项 (Nuxt 2 常用,Nuxt 3/4 更多通过 `nuxt generate` 命令控制)
});

写法差异总结:

  • Nuxt 4 通过其内置的构建工具和约定,极大地简化了 SSR 和 SSG 的实现。Vue 3 需要大量手动配置才能实现类似的功能。

4. 路由与导航

Vue 3 (Vue Router)

Vue 3 通常与 Vue Router 配合使用,需要手动定义路由配置。路由的定义和导航都通过编程方式进行。

// router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import Home from '../views/Home.vue';
import About from '../views/About.vue';

const routes = [
  { path: '/', name: 'Home', component: Home },
  { path: '/about', name: 'About', component: About },
];

const router = createRouter({
  history: createWebHistory(),
  routes,
});

export default router;
<!-- Vue 3 组件中导航 -->
<template>
  <div>
    <router-link to="/">Home</router-link>
    <router-link to="/about">About</router-link>
    <button @click="goToContact">Go to Contact</button>
  </div>
</template>

<script setup>
import { useRouter } from 'vue-router';

const router = useRouter();

const goToContact = () => {
  router.push('/contact');
};
</script>

Nuxt 4 (文件系统路由)

Nuxt 4 实现了基于文件系统的自动路由。开发者只需在 pages/ 目录下创建 .vue 文件,Nuxt 就会自动为其生成对应的路由。这大大减少了手动配置路由的工作量。

示例:pages/index.vuepages/about.vue

<!-- pages/index.vue -->
<template>
  <div>
    <h1>Welcome to the Home Page!</h1>
  </div>
</template>
<!-- pages/about.vue -->
<template>
  <div>
    <h1>About Us</h1>
  </div>
</template>

自动生成的路由:

  • / 对应 pages/index.vue
  • /about 对应 pages/about.vue

动态路由示例:pages/users/[id].vue

<!-- pages/users/[id].vue -->
<template>
  <div>
    <h1>User Profile: {{ route.params.id }}</h1>
  </div>
</template>

<script setup>
import { useRoute } from '#app'; // Nuxt 4 自动导入的 useRoute

const route = useRoute();
console.log('User ID:', route.params.id);
</script>

Nuxt 4 中的导航:

Nuxt 4 提供了 <NuxtLink> 组件用于导航,以及 navigateTo 组合式函数进行编程导航。

<!-- Nuxt 4 组件中导航 -->
<template>
  <div>
    <NuxtLink to="/">Home</NuxtLink>
    <NuxtLink to="/about">About</NuxtLink>
    <button @click="goToContact">Go to Contact</button>
  </div>
</template>

<script setup>
import { navigateTo } from '#app'; // Nuxt 4 自动导入的 navigateTo

const goToContact = () => {
  navigateTo('/contact');
};
</script>

写法差异总结:

  • Nuxt 4 的文件系统路由是其核心特性之一,极大地简化了路由配置。Vue 3 需要手动配置 Vue Router。
  • Nuxt 4 提供了 <NuxtLink>navigateTo 等专为通用应用设计的导航工具。

5. 状态管理

Vue 3 (Pinia 或 Vuex)

Vue 3 推荐使用 Pinia 作为官方状态管理库,它提供了更简洁的 API 和更好的 TypeScript 支持。Vuex 也可以继续使用。

Pinia 示例:stores/counter.js

// stores/counter.js
import { defineStore } from 'pinia';

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0,
  }),
  actions: {
    increment() {
      this.count++;
    },
  },
  getters: {
    doubleCount: (state) => state.count * 2,
  },
});

在 Vue 3 组件中使用 Pinia:

<!-- Vue 3 组件中使用 Pinia -->
<template>
  <div>
    <p>Count: {{ counter.count }}</p>
    <p>Double Count: {{ counter.doubleCount }}</p>
    <button @click="counter.increment()">Increment</button>
  </div>
</template>

<script setup>
import { useCounterStore } from '../stores/counter';

const counter = useCounterStore();
</script>

Nuxt 4 (内置 Pinia 支持)

Nuxt 4 推荐使用 Pinia,并提供了开箱即用的集成。你只需在 nuxt.config.ts 中添加 @pinia/nuxt 模块,然后就可以在 store/ 目录下定义 Pinia store。

nuxt.config.ts 配置:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@pinia/nuxt',
  ],
});

Pinia 示例:store/user.ts

// store/user.ts
import { defineStore } from 'pinia';

export const useUserStore = defineStore('user', {
  state: () => ({
    loggedIn: false,
    username: null as string | null,
  }),
  actions: {
    async login(username: string) {
      // 模拟登录逻辑
      return new Promise<void>(resolve => {
        setTimeout(() => {
          this.loggedIn = true;
          this.username = username;
          resolve();
        }, 1000);
      });
    },
    logout() {
      this.loggedIn = false;
      this.username = null;
    },
  },
  getters: {
    isLoggedIn: (state) => state.loggedIn,
    getUsername: (state) => state.username,
  },
});

在 Nuxt 4 组件中使用 Pinia:

<!-- Nuxt 4 组件中使用 Pinia -->
<template>
  <div>
    <h1 v-if="!userStore.isLoggedIn">Login</h1>
    <h1 v-else>Welcome, {{ userStore.getUsername }}!</h1>
    <button v-if="!userStore.isLoggedIn" @click="loginUser">Login</button>
    <button v-else @click="logoutUser">Logout</button>
  </div>
</template>

<script setup>
import { useUserStore } from '~/store/user'; // Nuxt 4 自动导入

const userStore = useUserStore();

const loginUser = async () => {
  await userStore.login('testuser');
};

const logoutUser = () => {
  userStore.logout();
};
</script>

写法差异总结:

  • Nuxt 4 内置了对 Pinia 的支持,简化了状态管理的配置和使用。Vue 3 需要手动安装和配置 Pinia 或 Vuex。
  • 在 Nuxt 4 中,store/ 目录下的 Pinia store 会被自动注册和导入。

6. 中间件 (Middleware)

Vue 3 (Vue Router 导航守卫)

在 Vue 3 中,路由守卫(beforeEach, beforeResolve, afterEach)用于在路由跳转前后执行逻辑,例如权限验证、登录检查等。这些守卫通常在 router/index.js 中定义。

// router/index.js
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAuth && !isAuthenticated()) {
    next('/login');
  } else {
    next();
  }
});

function isAuthenticated() {
  // 模拟认证逻辑
  return localStorage.getItem('token') !== null;
}

Nuxt 4 (路由中间件与服务端中间件)

Nuxt 4 提供了两种类型的中间件:

  1. 路由中间件 (Route Middleware):在页面或布局渲染之前运行,可以在服务端和客户端执行。用于路由级别的逻辑,如认证、重定向等。

    示例:middleware/auth.ts

    // middleware/auth.ts
    export default defineNuxtRouteMiddleware((to, from) => {
      const userStore = useUserStore(); // 假设已配置 Pinia
      if (!userStore.isLoggedIn && to.path !== '/login') {
        return navigateTo('/login');
      }
    });
    

    如何在页面中使用:

    <!-- pages/dashboard.vue -->
    <template>
      <div>
        <h1>Dashboard</h1>
        <p>Welcome, authenticated user!</p>
      </div>
    </template>
    
    <script setup>
    definePageMeta({
      middleware: ['auth'] // 应用 auth 中间件
    });
    </script>
    
  2. 服务端中间件 (Server Middleware):在 server/middleware/ 目录下定义,仅在服务端运行。用于处理传入的 HTTP 请求,例如日志记录、请求头处理、API 认证等。

    示例:server/middleware/log.ts

    // server/middleware/log.ts
    export default defineEventHandler((event) => {
      console.log('New request:', event.node.req.url);
    });
    

写法差异总结:

  • Nuxt 4 的中间件机制提供了更灵活和强大的方式来处理路由级别和服务端的逻辑,并且可以在服务端执行,这对于 SSR 应用的权限控制、日志记录等场景非常重要。Vue 3 的路由守卫主要在客户端执行。

7. 布局 (Layouts)

Vue 3

Vue 3 本身没有内置布局的概念。开发者通常会创建一些公共组件(如 Header.vue, Footer.vue),然后在每个页面组件中手动组合这些组件来构建页面布局,或者使用插槽 (slots) 来实现。

<!-- components/AppLayout.vue (自定义布局组件) -->
<template>
  <div>
    <header>My App Header</header>
    <slot></slot> <!-- 页面内容会渲染到这里 -->
    <footer>My App Footer</footer>
  </div>
</template>
<!-- views/Home.vue (使用自定义布局) -->
<template>
  <AppLayout>
    <h1>Home Page Content</h1>
    <p>This is the main content of the home page.</p>
  </AppLayout>
</template>

<script setup>
import AppLayout from '../components/AppLayout.vue';
</script>

Nuxt 4

Nuxt 4 提供了布局的概念,存放在 layouts/ 目录下。布局组件定义了页面的整体结构,页面组件通过 <NuxtLayout> 组件或在页面组件中指定 layout 属性来使用特定的布局。这使得管理和维护不同页面的公共结构变得非常方便。

示例:layouts/custom.vue

<!-- layouts/custom.vue -->
<template>
  <div>
    <header style="background-color: #e0f7fa; padding: 10px; border-bottom: 1px solid #b2ebf2;">
      Custom Layout Header
    </header>
    <main style="padding: 20px;">
      <slot />
    </main>
    <footer style="background-color: #e0f7fa; padding: 10px; border-top: 1px solid #b2ebf2; text-align: center;">
      Custom Layout Footer
    </footer>
  </div>
</template>

如何在页面中使用:

<!-- pages/my-page.vue -->
<template>
  <div>
    <h1>My Page Content</h1>
    <p>This page uses the custom layout.</p>
  </div>
</template>

<script setup>
definePageMeta({
  layout: 'custom' // 使用 custom 布局
});
</script>

写法差异总结:

  • Nuxt 4 内置的布局系统提供了一种结构化的方式来管理页面的公共部分,减少了重复代码。Vue 3 需要手动组合组件或创建自定义布局组件来实现。

8. 插件 (Plugins)

Vue 3

Vue 3 插件通常通过 app.use() 方法在 main.js/ts 中注册,用于扩展 Vue 的功能,例如添加全局方法、指令或组件。

// plugins/myPlugin.js
export default {
  install: (app, options) => {
    app.config.globalProperties.$myGlobalMethod = () => {
      console.log('Hello from global method!');
    };
  }
};
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import myPlugin from './plugins/myPlugin';

const app = createApp(App);
app.use(myPlugin);
app.mount('#app');

Nuxt 4

Nuxt 4 的插件存放在 plugins/ 目录下。这些插件会在 Vue 根实例实例化之前运行,并且可以在服务端和客户端都被注入。Nuxt 4 提供了 defineNuxtPlugin 辅助函数来定义插件。

示例:plugins/my-global-helper.ts

// plugins/my-global-helper.ts
export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.provide('myHelper', (message: string) => {
    console.log(`[MyHelper] ${message}`);
  });
});

如何在 Nuxt 4 组件中使用:

<!-- pages/index.vue -->
<template>
  <div>
    <h1>Plugin Example</h1>
  </div>
</template>

<script setup>
const { $myHelper } = useNuxtApp();

onMounted(() => {
  $myHelper('This is a message from the Nuxt plugin.');
});
</script>

写法差异总结:

  • Nuxt 4 的插件系统允许在应用启动前统一配置和注入功能,并且支持服务端和客户端的执行,这对于通用应用非常重要。Vue 3 插件主要在客户端注册。
  • Nuxt 4 提供了 defineNuxtPluginuseNuxtApp 等组合式函数来简化插件的定义和使用。

9. Meta 标签管理与 SEO

Vue 3

在 Vue 3 单页应用中,管理页面标题和 meta 标签通常需要借助第三方库,如 vue-meta (Vue 2) 或 vue-use-head (Vue 3) 或手动操作 DOM。这对于 SEO 来说是一个挑战,因为搜索引擎爬虫可能无法完全解析 JavaScript 渲染的内容。

Nuxt 4

Nuxt 4 内置了对 meta 标签管理的强大支持,通过 useHeaduseSeoMeta 组合式函数可以在组件级别轻松管理页面标题、描述、关键词等。这些 meta 标签会在服务端渲染时被正确地添加到 HTML 中,从而极大地改善 SEO。

示例:pages/product/[id].vue

<!-- pages/product/[id].vue -->
<template>
  <div>
    <h1>{{ product?.name }}</h1>
    <p>{{ product?.description }}</p>
  </div>
</template>

<script setup>
import { useRoute, useHead, useSeoMeta } from '#app';

const route = useRoute();
const productId = route.params.id;

// 模拟数据获取
const product = ref({
  name: `Product ${productId}`,
  description: `Details for product ${productId}.`,
});

// 使用 useHead 管理页面标题和 link 标签
useHead({
  title: `Product ${productId} - My Nuxt Shop`,
  link: [
    { rel: 'canonical', href: `https://example.com/product/${productId}` }
  ]
});

// 使用 useSeoMeta 管理 SEO 相关的 meta 标签
useSeoMeta({
  description: `Buy ${product.value.name} online.`, // 动态描述
  ogTitle: product.value.name,
  ogDescription: product.value.description,
  ogImage: 'https://example.com/product-image.jpg',
  twitterCard: 'summary_large_image',
});
</script>

写法差异总结:

  • Nuxt 4 提供了开箱即用的 useHeaduseSeoMeta 组合式函数,并且与 SSR 结合,对 SEO 非常友好。Vue 3 需要额外配置和处理。

10. 模块系统 (Modules)

Vue 3

Vue 3 本身没有内置的模块系统,通常通过 npm 包管理器安装和使用第三方库。

Nuxt 4

Nuxt 4 拥有一个强大的模块系统,允许开发者轻松地集成和扩展 Nuxt 的功能。许多社区模块提供了各种开箱即用的功能,如 PWA 支持、图片优化、认证、内容管理等。通过 nuxt.config.ts 配置模块,可以大大简化开发流程。

nuxt.config.ts 配置示例:

// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    // 示例:添加 Pinia 模块
    '@pinia/nuxt',
    // 示例:添加 Nuxt Content 模块
    '@nuxt/content',
    // 示例:添加 Nuxt Image 模块
    '@nuxt/image',
  ],
});

写法差异总结:

  • Nuxt 4 的模块系统提供了一种标准化的方式来扩展框架功能,使得集成第三方服务和功能更加便捷。Vue 3 依赖于手动安装和配置 npm 包。

11. 服务端路由与 API (Server Routes & API)

Vue 3

纯 Vue 3 应用是客户端应用,不直接处理服务端路由和 API。如果需要后端 API,通常需要单独搭建一个 Node.js (如 Express, Koa) 或其他语言的后端服务。

Nuxt 4

Nuxt 4 (基于 Nitro 引擎) 内置了强大的服务端能力,允许开发者在 server/ 目录下直接定义 API 路由和处理逻辑,无需单独的后端项目。这使得构建全栈应用变得非常方便。

示例:server/api/hello.ts (GET 请求)

// server/api/hello.ts
export default defineEventHandler(() => {
  return { message: 'Hello from Nuxt 4 API!' };
});

如何在客户端调用:

<!-- pages/index.vue -->
<template>
  <div>
    <h1>{{ apiMessage }}</h1>
    <button @click="fetchApi">Fetch API</button>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const apiMessage = ref('');

const fetchApi = async () => {
  try {
    const response = await $fetch('/api/hello');
    apiMessage.value = response.message;
  } catch (error) {
    console.error('Error fetching API:', error);
  }
};
</script>

示例:server/api/submit.post.ts (POST 请求)

// server/api/submit.post.ts
export default defineEventHandler(async (event) => {
  const body = await readBody(event);
  console.log('Received data:', body);
  return { status: 'success', receivedData: body };
});

如何在客户端调用:

<!-- pages/form.vue -->
<template>
  <div>
    <input v-model="formData.name" placeholder="Name" />
    <button @click="submitForm">Submit</button>
    <p>{{ submitStatus }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue';

const formData = ref({
  name: '',
});
const submitStatus = ref('');

const submitForm = async () => {
  try {
    const response = await $fetch('/api/submit', {
      method: 'POST',
      body: formData.value,
    });
    submitStatus.value = `Submission ${response.status}. Received: ${JSON.stringify(response.receivedData)}`;
  } catch (error) {
    console.error('Error submitting form:', error);
    submitStatus.value = 'Submission failed.';
  }
};
</script>

写法差异总结:

  • Nuxt 4 内置了服务端路由和 API 功能,允许开发者在同一个项目中构建全栈应用,极大地简化了前后端协作。Vue 3 需要单独的后端服务。

12. 部署与构建

Vue 3

Vue 3 应用通常通过 npm run build (Vite 或 Vue CLI) 命令构建为静态文件,然后部署到任何静态文件服务器上。对于 SSR,需要额外的 Node.js 服务器配置和部署流程。

Nuxt 4

Nuxt 4 提供了更丰富的构建和部署模式,得益于其 Nitro 引擎,可以轻松部署到各种环境,包括 Node.js 服务器、无服务器函数 (Serverless Functions)、边缘计算 (Edge Computing) 等。

  • nuxt dev:开发模式,支持热更新。
  • nuxt build:构建生产环境的应用,生成 .output 目录。
  • nuxt start:启动生产环境构建的应用 (SSR 模式)。
  • nuxt generate:生成静态站点 (SSG 模式),输出到 dist 目录。

package.json 脚本示例:

{
  "scripts": {
    "dev": "nuxt dev",
    "build": "nuxt build",
    "start": "nuxt start",
    "generate": "nuxt generate",
    "preview": "nuxt preview"
  }
}

写法差异总结:

  • Nuxt 4 提供了更统一和简化的构建与部署命令,特别针对通用应用和各种部署环境进行了优化。Vue 3 的部署主要集中在客户端静态文件的部署。

总结

| 特性/方面 | Vue 3 (客户端渲染) | Nuxt 4 (通用应用框架) Nuxt.js 是一个基于 Vue.js 的 的通用应用框架,可用于创建服务端渲染 (SSR) 应用、静态站点生成 (SSG) 应用以及单页应用 (SPA)。它在 Vue.js 的基础上提供了许多开箱即用的功能和约定,旨在简化 Vue.js 应用的开发。

Vue.js 更像是一个库,它只关注视图层,提供构建用户界面的核心功能。开发者可以自由选择路由、状态管理、构建工具等生态系统中的其他库来搭配使用。这使得 Vue.js 非常灵活,但也意味着开发者需要自行处理更多的配置和集成工作。

Nuxt.js 则是一个框架,它在 Vue.js 的基础上,集成了 Vue Router、Vuex、Webpack 等,并提供了约定式的目录结构、自动路由、数据获取钩子、中间件、布局等功能。Nuxt.js 的目标是提供一个完整的解决方案,让开发者能够更专注于业务逻辑的实现,而不是底层配置。

主要区别总结:

  • 定位与功能: Vue.js 是一个渐进式 JavaScript 框架,主要用于构建客户端单页应用。Nuxt.js 是一个基于 Vue.js 的更高层框架,专注于通用应用(SSR/SSG/SPA)的开发,提供了更多的约定和开箱即用的功能。
  • 项目结构与约定: Nuxt.js 强制执行一套约定俗成的目录结构,如 pages 用于自动路由,layouts 用于布局,store 用于 Vuex 等。Vue.js 项目结构相对自由,但需要手动配置。
  • 路由: Nuxt.js 实现了基于文件系统的自动路由,无需手动配置。Vue.js 需要手动配置 vue-router
  • 数据获取: Nuxt.js 提供了 asyncData/useAsyncDatafetch/useFetch 等钩子,支持在服务端或客户端进行数据预取,以实现 SSR 和 SSG。Vue.js 在客户端渲染模式下通常在组件挂载后获取数据。
  • 服务端渲染 (SSR) 与静态站点生成 (SSG): Nuxt.js 内置了对 SSR 和 SSG 的支持,简化了通用应用的开发和部署。Vue.js 需要额外配置才能实现 SSR。
  • 中间件: Nuxt.js 提供了中间件机制,可以在页面或布局渲染之前执行逻辑,支持服务端和客户端。Vue.js 主要通过路由守卫在客户端执行路由逻辑。
  • 布局: Nuxt.js 内置了布局系统,方便管理页面的公共结构。Vue.js 需要手动组合组件来实现布局。
  • 插件: Nuxt.js 的插件系统允许在应用启动前统一配置和注入功能,支持服务端和客户端。Vue.js 插件主要在客户端注册。
  • Meta 标签管理与 SEO: Nuxt.js 内置了对 meta 标签管理的强大支持,对 SEO 非常友好。Vue.js 需要额外配置。
  • 模块系统: Nuxt.js 拥有强大的模块系统,方便集成和扩展功能。Vue.js 依赖于手动安装和配置 npm 包。
  • 部署与构建: Nuxt.js 提供了更丰富的构建和部署选项,特别是针对 SSR 和 SSG 的优化。

总的来说,如果你需要构建一个简单的客户端渲染的单页应用,或者希望对项目结构有完全的自由度,那么 Vue.js 是一个很好的选择。而如果你需要构建一个支持服务端渲染、静态站点生成,并且希望快速启动和开发的企业级应用,那么 Nuxt.js 将会是更高效和便捷的选择,因为它提供了大量的开箱即用功能和约定,减少了开发者的配置工作。

Vue 3 和 Nuxt 4 的主要区别在于,Vue 3 是一个灵活的核心库,而 Nuxt 4 是一个功能完备的全栈框架。Nuxt 4 在 Vue 3 的基础上提供了大量约定和开箱即用的功能,极大地简化了通用应用的开发,特别是在服务端渲染、静态站点生成、路由管理、数据获取和 SEO 优化等方面。选择哪个框架取决于项目的具体需求:对于需要高度定制化或纯客户端渲染的简单应用,Vue 3 可能是更好的选择;而对于需要快速开发、功能丰富且对 SEO 有要求的全栈应用,Nuxt 4 则更具优势。

13. 应用入口与外壳:main.ts vs app.vue、RouterView vs NuxtPage

  • Vue 3:入口通常在 main.ts/main.js,在此创建应用、挂载路由与状态,根组件 App.vue 内部一般用 <RouterView/> 占位。
// main.ts (Vue 3)
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import { createPinia } from 'pinia'

createApp(App)
  .use(router)
  .use(createPinia())
  .mount('#app')
<!-- App.vue (Vue 3) -->
<template>
  <RouterView />
</template>
  • Nuxt 4:没有手写 main.ts;应用外壳由 app.vue 承担,页面容器用 <NuxtPage/>,布局容器用 <NuxtLayout/>
<!-- app.vue (Nuxt 4) -->
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>
</template>

差异要点:Vue 3 需要手动创建应用与注册插件;Nuxt 4 将应用生命周期、路由与渲染外壳抽象为约定组件。

14. 组合式 API 自动导入与路径别名

  • Vue 3:在 SFC 中需要手动 import { ref, computed } from 'vue',路径别名通常依赖 vite.config.ts 配置,如 @ 指向 src/
// vite.config.ts (Vue 3)
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src')
    }
  }
})
  • Nuxt 4:默认自动导入常用的 Vue 组合式 API 与 Nuxt 组合式 API(可从虚拟模块 #imports 显式导入);路径别名内置:~/@/ 指向项目根,#app#imports 提供框架级导入。
<!-- Nuxt 4:无需手动导入 ref/computed 等 -->
<script setup>
const count = ref(0)
const double = computed(() => count.value * 2)
</script>
// 也可选择性显式导入(类型推导更清晰)
import { ref, computed } from '#imports'

差异要点:Nuxt 4 大量自动导入减少模板样板代码,并提供 #app#imports 等框架别名;Vue 3 需自行配置别名与导入。

15. 路由约定增强:动态/可选/捕获全部与路由分组

  • Vue 3:手写 vue-router 配置,动态参数以 :id 形式出现在 route 定义;可选/捕获全部需要正则或多路由匹配。
// router/index.ts (Vue 3)
const routes = [
  { path: '/users/:id', component: () => import('@/views/User.vue') },
  { path: '/docs/:slug(.*)', component: () => import('@/views/Docs.vue') },
]
  • Nuxt 4:文件系统路由增强:
    • 动态参数:pages/users/[id].vue
    • 可选参数:pages/blog/[slug].vue + 逻辑判断,或 pages/blog/[[slug]].vue(可选段)
    • 捕获全部:pages/docs/[...slug].vue
    • 路由分组(不影响 URL,仅用于组织):pages/(marketing)/about.vue
pages/
  index.vue               -> /
  users/[id].vue          -> /users/:id
  docs/[...slug].vue      -> /docs/*
  (marketing)/about.vue   -> /about

差异要点:Nuxt 4 通过文件命名实现高级路由模式,减少手写 router 配置。

16. 全局样式与资源:import vs nuxt.config.ts

  • Vue 3:在 main.ts 里直接 import 全局样式或在各 SFC 中引入。
// main.ts (Vue 3)
import './assets/main.css'
  • Nuxt 4:在 nuxt.config.ts 中集中声明全局样式,自动注入客户端与 SSR。
// nuxt.config.ts (Nuxt 4)
export default defineNuxtConfig({
  css: ['~/assets/main.css', '~/assets/theme.scss']
})

差异要点:Nuxt 4 以配置驱动全局样式注入,避免分散在入口文件。

17. 运行时配置与环境变量:import.meta.env vs useRuntimeConfig

  • Vue 3:使用 Vite 的 import.meta.env 读取编译期注入的环境变量。
// 任意 Vue 3 组件/模块
const apiBase = import.meta.env.VITE_API_BASE
  • Nuxt 4:通过 runtimeConfig 管理运行时配置,区分私有与公开字段;在服务端和客户端安全可用。
// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    apiSecret: process.env.API_SECRET, // 仅服务端可见
    public: {
      apiBase: process.env.NUXT_PUBLIC_API_BASE || 'https://api.example.com'
    }
  }
})
// 组件/服务器处理器中 (Nuxt 4)
const config = useRuntimeConfig()
const base = config.public.apiBase

差异要点:Nuxt 4 的 runtimeConfig 适配 SSR/边缘运行环境;Vue 3 依赖构建期注入的 env。

18. 页面 Meta 与默认应用配置:definePageMeta、useHead、app.config.ts

  • Vue 3:一般用第三方库(如 @vueuse/head)或手动改 document.title
  • Nuxt 4:页面级 definePageMeta、组合式 useHead,以及应用级 app.config.ts 定义默认 head/主题等。
<!-- pages/product/[id].vue (Nuxt 4) -->
<script setup>
const route = useRoute()
useHead({ title: `Product ${route.params.id}` })

definePageMeta({
  layout: 'custom',
  middleware: ['auth'],
  pageTransition: { name: 'fade', mode: 'out-in' }
})
</script>
// app.config.ts (Nuxt 4)
export default defineAppConfig({
  ui: { primary: '#0ea5e9' },
  head: {
    meta: [{ name: 'viewport', content: 'width=device-width, initial-scale=1' }]
  }
})

差异要点:Nuxt 4 原生提供多层级 head/meta 配置与页面选项;Vue 3 依赖外部库。

19. 客户端专用与服务端专用:ClientOnly、插件后缀与中间件后缀

  • Vue 3:没有内置的 <ClientOnly> 组件;需自行判断 typeof window !== 'undefined'
  • Nuxt 4:提供 <ClientOnly> 组件避免 SSR 期间渲染依赖浏览器 API 的内容;插件/中间件可用文件后缀限定运行环境。
<!-- Nuxt 4 -->
<template>
  <ClientOnly>
    <ThirdPartyWidget />
  </ClientOnly>
</template>
plugins/analytics.client.ts   // 仅在客户端运行
plugins/db.server.ts          // 仅在服务端运行
middleware/auth.global.ts     // 全局路由中间件

差异要点:Nuxt 4 提供环境感知的组件与约定式文件后缀,SSR 安全性更强。

20. 通用状态与跨请求状态:useState 与 provide/inject/Pinia

  • Vue 3:组件外共享状态通常使用 Pinia/Vuex,或 provide/inject 手动传递。
  • Nuxt 4:提供 useState 在 SSR/CSR 间安全共享同名状态键,并在客户端水合。
// Nuxt 4 组件
const counter = useState('counter', () => 0)
function inc() { counter.value++ }

对比:useState 更贴合通用应用注水/脱水机制;复杂业务仍可结合 Pinia。

21. 服务器 API 与工具:H3 工具集

  • Vue 3:需要单独后端(Express/Koa 等)或 Vite SSR 才能处理服务端逻辑。
  • Nuxt 4:内置基于 Nitro/H3 的服务器运行时,server/api/*.ts 即 API 路由;提供丰富工具。
// server/api/user.get.ts (Nuxt 4)
export default defineEventHandler((event) => {
  const id = getQuery(event).id
  setHeader(event, 'x-powered-by', 'nuxt')
  return { id, time: Date.now() }
})

可用工具:getQuerygetRouterParamreadBodysetHeadersetCookie/getCookie 等,支持边缘/无服务部署。

22. 数据获取细节:useAsyncData/useFetch 选项与缓存

  • useAsyncData(key, handler, options) 常用选项:
    • server(是否仅在服务端执行)、lazy(路由切换时懒加载)、default(占位默认值)、transform(对结果做投影)、pickwatch(依赖变更自动刷新)等。
// Nuxt 4:带选项的数据获取
const { data, pending, error, refresh } = await useAsyncData(
  'products',
  () => $fetch(`${useRuntimeConfig().public.apiBase}/products`),
  { lazy: true, default: () => [], transform: d => d.items }
)
  • useNuxtData(key):读取已缓存的数据(避免重复请求)。

对比:Vue 3 需要自行实现缓存/并发去重;Nuxt 4 内建键控与缓存策略,更贴合 SSR。

23. 路由守卫对比:Vue Router 守卫 vs Nuxt 路由中间件

  • Vue 3:router.beforeEach/afterEach 全局守卫,或组件内 onBeforeRouteLeave
// 组件内 (Vue 3)
onBeforeRouteLeave((to, from) => {
  if (hasUnsaved) return confirm('Leave?')
})
  • Nuxt 4:推荐使用 middleware/ 下的命名或全局中间件;页面内通过 definePageMeta({ middleware: [] }) 声明,也可用组件内 onBeforeRouteLeave(沿用 Vue Router API)。

24. 错误处理与错误页:createError/useError/error.vue

  • Vue 3:可用 onErrorCaptured 捕获子组件错误,或路由 404 通过兜底路由实现。
onErrorCaptured((err) => {
  console.error(err)
})
  • Nuxt 4:提供错误页与错误组合式:
// 任意服务端/客户端抛错(Nuxt 4)
throw createError({ statusCode: 404, statusMessage: 'Not Found' })
<!-- error.vue (Nuxt 4 根目录) -->
<script setup>
const error = useError()
const handle = () => clearError({ redirect: '/' })
</script>

<template>
  <div>
    <h1>{{ error?.statusCode }} {{ error?.statusMessage }}</h1>
    <button @click="handle">Back Home</button>
  </div>
</template>

对比:Nuxt 4 标准化错误通道并集成到路由/SSR,Vue 3 需要手动搭建。

25. 资源目录与静态文件:assets vs public

  • Vue 3:public/ 直接映射为根路径,assets/ 通过打包处理;模板内引入通常依赖打包器解析。
  • Nuxt 4:同样存在 assets/(经打包处理)与 public/(原样复制直出)。
<!-- Nuxt 4 模板中使用公共文件 -->
<template>
  <img src="/logo.svg" alt="logo" />
</template>
<!-- 通过 assets 参与构建与指纹 -->
<template>
  <img :src="new URL('~/assets/images/hero.png', import.meta.url).href" alt="hero" />
</template>

26. 页面/布局过渡:pageTransition/layoutTransition

  • Vue 3:使用 <Transition> 包裹 <RouterView> 或页面组件,自行管理类名与动画。
  • Nuxt 4:通过 definePageMetaapp.config.ts 声明默认过渡。
// app.config.ts (Nuxt 4)
export default defineAppConfig({
  pageTransition: { name: 'page', mode: 'out-in' },
  layoutTransition: { name: 'layout', mode: 'out-in' }
})
<!-- 全局样式中定义过渡类 -->
<style>
.page-enter-active, .page-leave-active { transition: opacity .2s }
.page-enter-from, .page-leave-to { opacity: 0 }
</style>

27. 插件注入与类型:defineNuxtPlugin / useNuxtApp

  • Vue 3:通过 app.use() 安装插件,并通过 app.config.globalProperties 注入全局。
  • Nuxt 4:使用 defineNuxtPlugin 注入,使用 useNuxtApp() 访问,约定 $ 前缀可读性更强;可通过类型声明扩展。
// plugins/api.ts (Nuxt 4)
export default defineNuxtPlugin(() => {
  const api = $fetch.create({ baseURL: useRuntimeConfig().public.apiBase })
  return { provide: { api } }
})
// 类型增强(可选)
declare module '#app' {
  interface NuxtApp { $api: ReturnType<typeof $fetch.create> }
}

28. Route Rules(按路由配置渲染/缓存/头)

  • Vue 3:需自行在服务器/反向代理层面配置。
  • Nuxt 4:在 nuxt.config.tsrouteRules 针对路径模式配置 SSR/静态化/缓存等。
// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true },            // 生成时预渲染首页
    '/blog/**': { swr: 3600 },           // 博文缓存 1h(stale-while-revalidate)
    '/admin/**': { ssr: true },          // 强制 SSR
    '/api/**': { cors: true }            // API 开启 CORS
  }
})

29. Suspense 与异步组件

  • Vue 3:可用 <Suspense> 包裹异步组件/数据。
  • Nuxt 4:页面默认即在 Suspense 环境中运行,useAsyncData 与页面渲染天然配合;若局部需要占位,可显式使用 <Suspense>
<!-- Vue 3 -->
<Suspense>
  <AsyncWidget />
  <template #fallback>Loading...</template>
</Suspense>
<!-- Nuxt 4(通常直接依赖 useAsyncData 的 pending 状态即可) -->
<template>
  <div>
    <p v-if="pending">Loading...</p>
    <Widget v-else :data="data" />
  </div>
</template>

30. 目录约定的完整示例(Nuxt 4)

assets/            # 未编译资源(scss/fonts/images)
components/        # 自动导入的 Vue 组件
composables/       # 自动导入的组合式函数
layouts/           # 页面布局
middleware/        # 路由中间件(.global.ts 为全局)
pages/             # 文件系统路由页面
plugins/           # defineNuxtPlugin 注入
public/            # 直接公开的静态资源(原路径映射)
server/            # 服务器功能(api/middleware/routes)
  api/             # H3 事件处理器(.get.ts/.post.ts)
  middleware/      # 服务器级中间件
store/             # Pinia stores(推荐)
app.vue            # 应用外壳
app.config.ts      # 默认 head/主题/过渡配置
nuxt.config.ts     # 框架配置

示例片段:

// composables/useHello.ts
export const useHello = () => 'hello' // 组件中可直接使用 useHello()
<!-- components/HelloBadge.vue -->
<template><span class="badge">{{ useHello() }}</span></template>
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware(() => {
  const user = useUserStore()
  if (!user.isLoggedIn) return navigateTo('/login')
})
// server/api/ping.get.ts
export default defineEventHandler(() => ({ ok: true }))
// plugins/dayjs.client.ts
import dayjs from 'dayjs'
export default defineNuxtPlugin(() => ({ provide: { dayjs } }))
<!-- pages/index.vue -->
<template>
  <div>
    <HelloBadge />
    <p>{{ $dayjs().format('YYYY-MM-DD') }}</p>
  </div>
</template>

<script setup>
const { $dayjs } = useNuxtApp()
</script>