本文详解如何用Vue.js作为前端、WordPress作为后端(Headless架构),实现前后端完全分离。

架构概览

[Vue.js SPA]  ←→  [WordPress REST API]  ←→  [MySQL]
     
     └───────────  用户浏览器  ──────────────────────┘

优势:
- 前端用Vue.js(极速交互)
- 后端用WordPress(内容管理成熟)
- 完全解耦,各自独立部署

第一步:准备WordPress后端

启用REST API(默认已启用)

// functions.php(确认REST API已启用)
add_action('init', function() {
    // 确保REST API不被禁用
    if (defined('REST_API_ENABLED') && !REST_API_ENABLED) {
        define('REST_API_ENABLED', true);
    }
});

// 允许跨域请求(开发环境)
add_action('rest_api_init', function() {
    remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');
    add_filter('rest_pre_serve_request', function($value) {
        header('Access-Control-Allow-Origin: *');
        header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
        header('Access-Control-Allow-Credentials: true');
        return $value;
    });
});

创建Vue.js专用API端点

// 注册自定义API端点(返回精简数据)
add_action('rest_api_init', 'register_vue_endpoint');

function register_vue_endpoint() {
    register_rest_route('vue/v1', '/posts', [
        'methods'  => 'GET',
        'callback' => 'get_posts_for_vue',
        'permission_callback' => '__return_true',
    ]);
}

function get_posts_for_vue($request) {
    $per_page = $request->get_param('per_page') ?: 10;

    $query = new WP_Query([
        'post_type'      => 'post',
        'posts_per_page' => $per_page,
        'post_status'    => 'publish',
    ]);

    $posts = [];
    foreach ($query->posts as $post) {
        $posts[] = [
            'id'        => $post->ID,
            'title'     => get_the_title($post),
            'excerpt'   => get_the_excerpt($post),
            'content'   => apply_filters('the_content', $post->post_content),
            'featured_image' => get_the_post_thumbnail_url($post, 'full'),
            'date'      => get_the_date('c', $post),
            'author'    => get_the_author_meta('display_name', $post->post_author),
            'categories' => wp_get_post_categories($post->ID, ['fields' => 'names']),
        ];
    }

    return rest_ensure_response($posts);
}

第二步:创建Vue.js前端

初始化Vue项目

# 使用Vite + Vue 3
npm create vite@latest wp-vue-frontend -- --template vue
cd wp-vue-frontend
npm install

# 安装必要依赖
npm install axios vue-router@4 pinia  # Pinia = Vue 3状态管理

配置环境变量

# .env.development
VITE_WP_API_URL=http://localhost:8000/wp-json
VITE_WP_API_NAMESPACE=vue/v1

# .env.production
VITE_WP_API_URL=https://www.shenma98.com/wp-json
VITE_WP_API_NAMESPACE=vue/v1

第三步:在Vue中获取WordPress数据

创建API服务层

// src/services/wpApi.js
import axios from 'axios';

const apiClient = axios.create({
  baseURL: import.meta.env.VITE_WP_API_URL,
  timeout: 10000,
});

export default {
  // 获取文章列表
  async getPosts(perPage = 10, page = 1) {
    const res = await apiClient.get(`/wp/v2/posts`, {
      params: { per_page: perPage, page },
    });
    return {
      posts: res.data,
      totalPages: parseInt(res.headers['x-wp-totalpages']),
      total: parseInt(res.headers['x-wp-total']),
    };
  },

  // 获取单个文章
  async getPost(slug) {
    const res = await apiClient.get('/wp/v2/posts', {
      params: { slug },
    });
    return res.data[0] || null;
  },

  // 获取页面
  async getPage(slug) {
    const res = await apiClient.get('/wp/v2/pages', {
      params: { slug },
    });
    return res.data[0] || null;
  },

  // 获取媒体文件
  async getMedia(mediaId) {
    const res = await apiClient.get(`/wp/v2/media/${mediaId}`);
    return res.data;
  },
};

创建文章列表页面

<!-- src/views/PostList.vue -->
<template>
  <div class="post-list">
    <h1>最新文章</h1>
    <div v-if="loading" class="loading">加载中...</div>
    <div v-else>
      <article v-for="post in posts" :key="post.id" class="post-card">
        <img 
          v-if="post._embedded?.['wp:featuredmedia']" 
          :src="post._embedded['wp:featuredmedia'][0].source_url" 
        />
        <h2>
          <router-link :to="{ name: 'post', params: { slug: post.slug } }">
            {{ post.title.rendered }}
          </router-link>
        </h2>
        <div v-html="post.excerpt.rendered"></div>
      </article>

      <!-- 分页 -->
      <div class="pagination">
        <button @click="prevPage" :disabled="currentPage <= 1">上一页</button>
        <span>{{ currentPage }} / {{ totalPages }}</span>
        <button @click="nextPage" :disabled="currentPage >= totalPages">下一页</button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import wpApi from '@/services/wpApi';

const posts = ref([]);
const loading = ref(true);
const currentPage = ref(1);
const totalPages = ref(1);

const loadPosts = async () => {
  loading.value = true;
  try {
    const res = await wpApi.getPosts(10, currentPage.value);
    posts.value = res.posts;
    totalPages.value = res.totalPages;
  } catch (err) {
    console.error('Failed to load posts:', err);
  } finally {
    loading.value = false;
  }
};

const prevPage = () => { if (currentPage.value > 1) { currentPage.value--; loadPosts(); } };
const nextPage = () => { if (currentPage.value < totalPages.value) { currentPage.value++; loadPosts(); } };

onMounted(loadPosts);
</script>

第四步:配置Vue Router

路由配置

// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router';
import PostList from '@/views/PostList.vue';
import PostDetail from '@/views/PostDetail.vue';
import PageView from '@/views/PageView.vue';

const routes = [
  { path: '/', name: 'home', component: PostList },
  { path: '/post/:slug', name: 'post', component: PostDetail, props: true },
  { path: '/:slug', name: 'page', component: PageView, props: true },
];

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

export default router;

文章详情页

<!-- src/views/PostDetail.vue -->
<template>
  <div class="post-detail" v-if="post">
    <h1>{{ post.title.rendered }}</h1>
    <div class="post-meta">
      <span>作者:{{ post._embedded?.author?.[0]?.name }}</span>
      <span>日期:{{ formatDate(post.date) }}</span>
    </div>
    <div class="post-content" v-html="post.content.rendered"></div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import wpApi from '@/services/wpApi';

const route = useRoute();
const post = ref(null);

const formatDate = (dateStr) => new Date(dateStr).toLocaleDateString('zh-CN');

onMounted(async () => {
  post.value = await wpApi.getPost(route.params.slug);
});
</script>

第五步:处理WordPress认证

方式一:Application Password(推荐)

// 在Vue中获取用户信息(需要认证)
const authClient = axios.create({
  baseURL: import.meta.env.VITE_WP_API_URL,
  auth: {
    username: 'your_username',
    password: 'xxxx xxxx xxxx xxxx',  // Application Password
  },
});

// 创建文章(需要认证)
async function createPost(title, content) {
  return authClient.post('/wp/v2/posts', {
    title,
    content,
    status: 'publish',
  });
}

方式二:JWT认证

# 在WordPress中安装 JWT Authentication for WP REST API 插件
// Vue中登录获取JWT Token
async function login(username, password) {
  const res = await axios.post(`${WP_URL}/jwt-auth/v1/token`, {
    username,
    password,
  });

  localStorage.setItem('jwt_token', res.data.token);
  return res.data.token;
}

// 在请求头中添加Token
apiClient.defaults.headers.common['Authorization'] = `Bearer ${localStorage.getItem('jwt_token')}`;

第六步:部署前后端

部署WordPress后端

# 正常部署WordPress(LAMP/LNMP)
# 确保REST API可访问:
curl https://www.shenma98.com/wp-json/wp/v2/posts

部署Vue.js前端

# 构建生产版本
npm run build  # 输出到 dist/

# 部署到静态托管(Vercel/Netlify/CDN)
vercel --prod

# 或者部署到自己的服务器
scp -r dist/* user@server:/var/www/html/vue-app/

配置Nginx反向代理(可选)

# 将 /api/ 请求代理到WordPress
location /api/ {
    proxy_pass https://www.shenma98.com/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
}

性能优化

1. 启用REST API缓存

// 在WordPress中启用Transient缓存
function get_cached_posts() {
    $cached = get_transient('cached_posts');
    if (false === $cached) {
        $query = new WP_Query(['post_type' => 'post', 'posts_per_page' => 10]);
        $cached = $query->posts;
        set_transient('cached_posts', $cached, 12 * HOUR_IN_SECONDS);
    }
    return $cached;
}

2. Vue.js侧缓存

// 使用Pinia持久化缓存
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';

const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);

// 现在store数据会自动保存到localStorage

3. 预获取(Prefetch)数据

<!-- 在文章列表页预获取文章内容 -->
<router-link 
  :to="{ name: 'post', params: { slug: post.slug } }"
  @mouseover="prefetchPost(post.id)"
>
  {{ post.title.rendered }}
</router-link>

<script setup>
function prefetchPost(postId) {
  // 预获取文章数据并缓存
  queryClient.prefetchQuery(['post', postId], () => wpApi.getPostById(postId));
}
</script>

2026年Headless WordPress趋势

趋势一:WordPress作为统一内容源

架构:
WordPress(内容管理) 
     REST API / GraphQL
Vue.js / React / Next.js(前端,可多个)
    Web / iOS / Android / 小程序(多端消费)

趋势二:使用WPGraphQL替代REST API

// 使用GraphQL查询(更灵活)
import { request } from 'graphql-request';

const query = `
  query GetPosts {
    posts(first: 10) {
      nodes {
        title
        excerpt
        featuredImage {
          sourceUrl
        }
      }
    }
  }
`;

const data = await request('https://example.com/graphql', query);

趋势三:ISR(增量静态再生)

// Next.js ISR:静态页面但定期更新
export async function getStaticProps() {
  const posts = await wpApi.getPosts();

  return {
    props: { posts },
    revalidate: 60,  // 60秒后重新生成
  };
}

决策建议

  • 新项目 + 高交互 → Vue.js + WordPress REST API(Headless)
  • 传统企业站 → 传统WordPress主题(更适合SEO)
  • 多端应用 → Headless WordPress + GraphQL
  • 快速原型 → Vue.js + WordPress REST API(开发快)

总结

WordPress REST API + Vue.js是2026年前后端分离的经典架构。WordPress负责内容管理,Vue.js负责交互体验,两者通过REST API通信。

立即行动:尝试用Vue.js创建一个简单的Headless WordPress前端,体验前后端分离的开发模式。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。