Vue Router 404页面配置:从基础到高级的完整指南
前言:为什么需要精心设计404页面?
404页面不只是"页面不存在"的提示,它还是:
- 🚨 用户体验的救生艇:用户迷路时的导航站
- 🔍 SEO优化的重要部分:正确处理404状态码
- 🎨 品牌展示的机会:体现产品设计的一致性
- 📊 数据分析的入口:了解用户访问的"死胡同"
今天,我们将从基础到高级,全面掌握Vue Router中的404页面配置技巧。
一、基础配置:创建你的第一个404页面
1.1 最简单的404页面配置
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import NotFound from '../views/NotFound.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/about',
name: 'About',
component: () => import('../views/About.vue')
},
// 404路由 - 必须放在最后
{
path: '/:pathMatch(.*)*', // Vue 3 新语法
name: 'NotFound',
component: NotFound
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
<!-- views/NotFound.vue -->
<template>
<div class="not-found">
<div class="error-code">404</div>
<h1 class="error-title">页面不存在</h1>
<p class="error-message">
抱歉,您访问的页面可能已被删除或暂时不可用。
</p>
<div class="action-buttons">
<router-link to="/" class="btn btn-primary">
返回首页
</router-link>
<button @click="goBack" class="btn btn-secondary">
返回上一页
</button>
</div>
</div>
</template>
<script>
export default {
name: 'NotFound',
methods: {
goBack() {
if (window.history.length > 1) {
this.$router.go(-1)
} else {
this.$router.push('/')
}
}
}
}
</script>
<style scoped>
.not-found {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 80vh;
text-align: center;
padding: 2rem;
}
.error-code {
font-size: 8rem;
font-weight: 900;
color: #e0e0e0;
line-height: 1;
margin-bottom: 1rem;
}
.error-title {
font-size: 2rem;
margin-bottom: 1rem;
color: #333;
}
.error-message {
font-size: 1.1rem;
color: #666;
margin-bottom: 2rem;
max-width: 500px;
}
.action-buttons {
display: flex;
gap: 1rem;
}
.btn {
padding: 0.75rem 1.5rem;
border-radius: 4px;
text-decoration: none;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-primary {
background-color: #1890ff;
color: white;
border: none;
}
.btn-primary:hover {
background-color: #40a9ff;
}
.btn-secondary {
background-color: transparent;
color: #666;
border: 1px solid #d9d9d9;
}
.btn-secondary:hover {
border-color: #1890ff;
color: #1890ff;
}
</style>
1.2 路由匹配模式详解
// Vue Router 的不同匹配模式
const routes = [
// Vue 3 推荐:匹配所有路径并捕获参数
{
path: '/:pathMatch(.*)*', // 捕获路径到 params.pathMatch
component: NotFound
},
// Vue 2 或 Vue 3 兼容
{
path: '*', // 旧版本语法,Vue 3 中仍然可用
component: NotFound
},
// 捕获特定模式
{
path: '/user-:userId(.*)', // 匹配 /user-xxx
component: UserProfile,
beforeEnter: (to) => {
// 可以在这里验证用户ID是否存在
if (!isValidUserId(to.params.userId)) {
return { path: '/404' }
}
}
},
// 嵌套路由中的404
{
path: '/dashboard',
component: DashboardLayout,
children: [
{
path: '', // 默认子路由
component: DashboardHome
},
{
path: 'settings',
component: DashboardSettings
},
{
path: ':pathMatch(.*)*', // 仪表板内的404
component: DashboardNotFound
}
]
}
]
二、中级技巧:智能404处理
2.1 动态404页面(根据错误类型显示不同内容)
<!-- views/NotFound.vue -->
<template>
<div class="not-found">
<!-- 根据错误类型显示不同内容 -->
<template v-if="errorType === 'product'">
<ProductNotFound :product-id="productId" />
</template>
<template v-else-if="errorType === 'user'">
<UserNotFound :username="username" />
</template>
<template v-else>
<GenericNotFound />
</template>
</div>
</template>
<script>
import GenericNotFound from '@/components/errors/GenericNotFound.vue'
import ProductNotFound from '@/components/errors/ProductNotFound.vue'
import UserNotFound from '@/components/errors/UserNotFound.vue'
export default {
name: 'NotFound',
components: {
GenericNotFound,
ProductNotFound,
UserNotFound
},
computed: {
// 从路由参数分析错误类型
errorType() {
const path = this.$route.params.pathMatch?.[0] || ''
if (path.includes('/products/')) {
return 'product'
} else if (path.includes('/users/')) {
return 'user'
} else if (path.includes('/admin/')) {
return 'admin'
}
return 'generic'
},
// 提取ID参数
productId() {
const match = this.$route.params.pathMatch?.[0].match(/\/products\/(\d+)/)
return match ? match[1] : null
},
username() {
const match = this.$route.params.pathMatch?.[0].match(/\/users\/(\w+)/)
return match ? match[1] : null
}
}
}
</script>
2.2 全局路由守卫中的404处理
// router/index.js
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
// ... 其他路由
{
path: '/404',
name: 'NotFoundPage',
component: () => import('@/views/NotFound.vue')
},
{
path: '/:pathMatch(.*)*',
redirect: (to) => {
// 可以在重定向前记录日志
log404Error(to.fullPath)
// 如果是API路径,返回API 404
if (to.path.startsWith('/api/')) {
return {
path: '/api/404',
query: { originalPath: to.fullPath }
}
}
// 否则返回普通404页面
return {
path: '/404',
query: { originalPath: to.fullPath }
}
}
}
]
})
// 全局前置守卫
router.beforeEach((to, from, next) => {
// 检查用户权限
if (to.meta.requiresAuth && !isAuthenticated()) {
next('/login')
return
}
// 检查路由是否存在(动态路由验证)
if (!isRouteValid(to)) {
// 重定向到404页面,并传递原始路径
next({
path: '/404',
query: {
originalPath: to.fullPath,
timestamp: new Date().getTime()
}
})
return
}
next()
})
// 全局后置守卫 - 用于分析和埋点
router.afterEach((to, from) => {
// 记录页面访问
analytics.trackPageView(to.fullPath)
// 如果是404页面,记录访问
if (to.name === 'NotFoundPage') {
track404Error({
path: to.query.originalPath,
referrer: from.fullPath,
userAgent: navigator.userAgent
})
}
})
2.3 异步路由验证
// 动态验证路由是否存在
async function isRouteValid(to) {
// 对于动态路由,需要验证参数是否有效
if (to.name === 'ProductDetail') {
try {
const productId = to.params.id
const isValid = await validateProductId(productId)
return isValid
} catch {
return false
}
}
// 对于静态路由,检查路由表
const matchedRoutes = router.getRoutes()
return matchedRoutes.some(route =>
route.path === to.path || route.regex.test(to.path)
)
}
// 路由配置示例
const routes = [
{
path: '/products/:id',
name: 'ProductDetail',
component: () => import('@/views/ProductDetail.vue'),
// 路由独享的守卫
beforeEnter: async (to, from, next) => {
try {
const productId = to.params.id
// 验证产品是否存在
const productExists = await checkProductExists(productId)
if (productExists) {
next()
} else {
// 产品不存在,重定向到404
next({
name: 'ProductNotFound',
params: { productId }
})
}
} catch (error) {
// API错误,重定向到错误页面
next({
name: 'ServerError',
query: { from: to.fullPath }
})
}
}
},
// 产品404页面(不是通用404)
{
path: '/products/:productId/not-found',
name: 'ProductNotFound',
component: () => import('@/views/ProductNotFound.vue'),
props: true
}
]
三、高级配置:企业级404解决方案
3.1 多层404处理架构
// router/index.js - 企业级路由配置
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
// 公共路由
{
path: '/',
component: () => import('@/layouts/PublicLayout.vue'),
children: [
{ path: '', component: () => import('@/views/Home.vue') },
{ path: 'about', component: () => import('@/views/About.vue') },
{ path: 'contact', component: () => import('@/views/Contact.vue') },
// 公共404
{ path: ':pathMatch(.*)*', component: () => import('@/views/PublicNotFound.vue') }
]
},
// 仪表板路由
{
path: '/dashboard',
component: () => import('@/layouts/DashboardLayout.vue'),
meta: { requiresAuth: true },
children: [
{ path: '', component: () => import('@/views/dashboard/Home.vue') },
{ path: 'profile', component: () => import('@/views/dashboard/Profile.vue') },
{ path: 'settings', component: () => import('@/views/dashboard/Settings.vue') },
// 仪表板内404
{ path: ':pathMatch(.*)*', component: () => import('@/views/dashboard/DashboardNotFound.vue') }
]
},
// 管理员路由
{
path: '/admin',
component: () => import('@/layouts/AdminLayout.vue'),
meta: { requiresAuth: true, requiresAdmin: true },
children: [
{ path: '', component: () => import('@/views/admin/Dashboard.vue') },
{ path: 'users', component: () => import('@/views/admin/Users.vue') },
{ path: 'analytics', component: () => import('@/views/admin/Analytics.vue') },
// 管理员404
{ path: ':pathMatch(.*)*', component: () => import('@/views/admin/AdminNotFound.vue') }
]
},
// 特殊错误页面
{
path: '/403',
name: 'Forbidden',
component: () => import('@/views/errors/Forbidden.vue')
},
{
path: '/500',
name: 'ServerError',
component: () => import('@/views/errors/ServerError.vue')
},
{
path: '/maintenance',
name: 'Maintenance',
component: () => import('@/views/errors/Maintenance.vue')
},
// 全局404 - 必须放在最后
{
path: '/:pathMatch(.*)*',
name: 'GlobalNotFound',
component: () => import('@/views/errors/GlobalNotFound.vue')
}
]
})
// 错误处理中间件
router.beforeEach(async (to, from, next) => {
// 维护模式检查
if (window.__MAINTENANCE_MODE__ && to.path !== '/maintenance') {
next('/maintenance')
return
}
// 权限检查
const requiresAuth = to.matched.some(record => record.meta.requiresAuth)
const requiresAdmin = to.matched.some(record => record.meta.requiresAdmin)
if (requiresAuth && !store.state.user.isAuthenticated) {
next('/login')
return
}
if (requiresAdmin && !store.state.user.isAdmin) {
next('/403')
return
}
// 动态路由验证
if (to.name === 'ProductDetail') {
const isValid = await validateProductRoute(to.params.id)
if (!isValid) {
// 重定向到产品专用404
next({
name: 'ProductNotFound',
params: { productId: to.params.id }
})
return
}
}
next()
})
3.2 SEO友好的404配置
<!-- views/errors/NotFound.vue -->
<template>
<div class="not-found">
<!-- 结构化数据,帮助搜索引擎理解 -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebPage",
"name": "404 Page Not Found",
"description": "The page you are looking for does not exist.",
"url": "https://yourdomain.com/404",
"isPartOf": {
"@type": "WebSite",
"name": "Your Site Name",
"url": "https://yourdomain.com"
}
}
</script>
<!-- 页面内容 -->
<div class="container">
<h1 class="error-title">404 - Page Not Found</h1>
<!-- 搜索建议 -->
<div class="search-suggestions" v-if="suggestions.length > 0">
<p>Were you looking for one of these?</p>
<ul class="suggestion-list">
<li v-for="suggestion in suggestions" :key="suggestion.path">
<router-link :to="suggestion.path">
{{ suggestion.title }}
</router-link>
</li>
</ul>
</div>
<!-- 热门内容 -->
<div class="popular-content">
<h3>Popular Pages</h3>
<div class="popular-grid">
<router-link
v-for="page in popularPages"
:key="page.path"
:to="page.path"
class="popular-card"
>
{{ page.title }}
</router-link>
</div>
</div>
</div>
</div>
</template>
<script>
import { ref, onMounted } from 'vue'
import { useRoute } from 'vue-router'
export default {
name: 'NotFound',
setup() {
const route = useRoute()
const suggestions = ref([])
const popularPages = ref([
{ path: '/', title: 'Home' },
{ path: '/products', title: 'Products' },
{ path: '/about', title: 'About Us' },
{ path: '/contact', title: 'Contact' }
])
// 分析路径,提供智能建议
onMounted(() => {
const path = route.query.originalPath || ''
// 提取可能的搜索关键词
const keywords = extractKeywords(path)
// 查找相关页面
if (keywords.length > 0) {
suggestions.value = findRelatedPages(keywords)
}
// 发送404事件到分析工具
send404Analytics({
path,
referrer: document.referrer,
suggestions: suggestions.value.length
})
})
return {
suggestions,
popularPages
}
}
}
</script>
<style scoped>
/* 确保搜索引擎不会索引404页面 */
.not-found {
/* 设置适当的HTTP状态码需要服务器端配合 */
}
/* 对于客户端渲染,可以在头部添加meta标签 */
</style>
// server.js - Node.js/Express 示例
const express = require('express')
const { createServer } = require('http')
const { renderToString } = require('@vue/server-renderer')
const { createApp } = require('./app')
const server = express()
// 为404页面设置正确的HTTP状态码
server.get('*', async (req, res, next) => {
const { app, router } = createApp()
await router.push(req.url)
await router.isReady()
const matchedComponents = router.currentRoute.value.matched
if (matchedComponents.length === 0) {
// 设置404状态码
res.status(404)
} else if (matchedComponents.some(comp => comp.name === 'NotFound')) {
// 明确访问/404页面时,也设置404状态码
res.status(404)
}
const html = await renderToString(app)
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>${router.currentRoute.value.name === 'NotFound' ? '404 - Page Not Found' : 'My App'}</title>
<meta name="robots" content="noindex, follow">
</head>
<body>
<div id="app">${html}</div>
</body>
</html>
`)
})
3.3 404页面数据分析与监控
// utils/errorTracking.js
class ErrorTracker {
constructor() {
this.errors = []
this.maxErrors = 100
}
// 记录404错误
track404(path, referrer = '') {
const error = {
type: '404',
path,
referrer,
timestamp: new Date().toISOString(),
userAgent: navigator.userAgent,
screenResolution: `${window.screen.width}x${window.screen.height}`,
language: navigator.language
}
this.errors.push(error)
// 限制存储数量
if (this.errors.length > this.maxErrors) {
this.errors.shift()
}
// 发送到分析服务器
this.sendToAnalytics(error)
// 存储到localStorage
this.saveToLocalStorage()
console.warn(`404 Error: ${path} from ${referrer}`)
}
// 发送到后端分析
async sendToAnalytics(error) {
try {
await fetch('/api/analytics/404', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(error)
})
} catch (err) {
console.error('Failed to send 404 analytics:', err)
}
}
// 获取404统计
get404Stats() {
const last24h = Date.now() - 24 * 60 * 60 * 1000
return {
total: this.errors.length,
last24h: this.errors.filter(e =>
new Date(e.timestamp) > last24h
).length,
commonPaths: this.getMostCommonPaths(),
commonReferrers: this.getMostCommonReferrers()
}
}
// 获取最常见的404路径
getMostCommonPaths(limit = 10) {
const pathCounts = {}
this.errors.forEach(error => {
pathCounts[error.path] = (pathCounts[error.path] || 0) + 1
})
return Object.entries(pathCounts)
.sort(([,a], [,b]) => b - a)
.slice(0, limit)
.map(([path, count]) => ({ path, count }))
}
// 保存到本地存储
saveToLocalStorage() {
try {
localStorage.setItem('404_errors', JSON.stringify(this.errors))
} catch (err) {
console.error('Failed to save 404 errors:', err)
}
}
// 从本地存储加载
loadFromLocalStorage() {
try {
const saved = localStorage.getItem('404_errors')
if (saved) {
this.errors = JSON.parse(saved)
}
} catch (err) {
console.error('Failed to load 404 errors:', err)
}
}
}
// 在Vue中使用
export default {
install(app) {
const tracker = new ErrorTracker()
tracker.loadFromLocalStorage()
app.config.globalProperties.$errorTracker = tracker
// 路由错误处理
app.config.errorHandler = (err, instance, info) => {
console.error('Vue error:', err, info)
tracker.trackError(err, info)
}
}
}
四、实用组件库:可复用的404组件
4.1 基础404组件
<!-- components/errors/Base404.vue -->
<template>
<div class="base-404" :class="variant">
<div class="illustration">
<slot name="illustration">
<Default404Illustration />
</slot>
</div>
<div class="content">
<h1 class="title">
<slot name="title">
{{ title }}
</slot>
</h1>
<p class="description">
<slot name="description">
{{ description }}
</slot>
</p>
<div class="actions">
<slot name="actions">
<BaseButton
variant="primary"
@click="goHome"
>
返回首页
</BaseButton>
<BaseButton
variant="outline"
@click="goBack"
>
返回上一页
</BaseButton>
</slot>
</div>
<div v-if="showSearch" class="search-container">
<SearchBar @search="handleSearch" />
</div>
</div>
</div>
</template>
<script>
import { computed } from 'vue'
import { useRouter } from 'vue-router'
import BaseButton from '../ui/BaseButton.vue'
import SearchBar from '../ui/SearchBar.vue'
import Default404Illustration from './illustrations/Default404Illustration.vue'
export default {
name: 'Base404',
components: {
BaseButton,
SearchBar,
Default404Illustration
},
props: {
variant: {
type: String,
default: 'default',
validator: (value) => ['default', 'compact', 'full'].includes(value)
},
title: {
type: String,
default: '页面不存在'
},
description: {
type: String,
default: '抱歉,您访问的页面可能已被删除或暂时不可用。'
},
showSearch: {
type: Boolean,
default: true
}
},
setup(props, { emit }) {
const router = useRouter()
const containerClass = computed(() => ({
'base-404--compact': props.variant === 'compact',
'base-404--full': props.variant === 'full'
}))
const goHome = () => {
emit('go-home')
router.push('/')
}
const goBack = () => {
emit('go-back')
if (window.history.length > 1) {
router.go(-1)
} else {
goHome()
}
}
const handleSearch = (query) => {
emit('search', query)
router.push(`/search?q=${encodeURIComponent(query)}`)
}
return {
containerClass,
goHome,
goBack,
handleSearch
}
}
}
</script>
<style scoped>
.base-404 {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 60vh;
padding: 2rem;
text-align: center;
}
.base-404--compact {
min-height: 40vh;
padding: 1rem;
}
.base-404--full {
min-height: 80vh;
padding: 3rem;
}
.illustration {
margin-bottom: 2rem;
max-width: 300px;
}
.base-404--compact .illustration {
max-width: 150px;
margin-bottom: 1rem;
}
.base-404--full .illustration {
max-width: 400px;
margin-bottom: 3rem;
}
.content {
max-width: 500px;
}
.title {
font-size: 2rem;
margin-bottom: 1rem;
color: #333;
}
.base-404--compact .title {
font-size: 1.5rem;
}
.base-404--full .title {
font-size: 2.5rem;
}
.description {
font-size: 1.1rem;
color: #666;
margin-bottom: 2rem;
line-height: 1.6;
}
.actions {
display: flex;
gap: 1rem;
justify-content: center;
margin-bottom: 2rem;
}
.search-container {
max-width: 400px;
margin: 0 auto;
}
</style>
4.2 智能404组件(带内容推荐)
<!-- components/errors/Smart404.vue -->
<template>
<Base404 :variant="variant" :title="title" :description="description">
<template #illustration>
<Animated404Illustration />
</template>
<template v-if="suggestions.length > 0" #description>
<div class="smart-description">
<p>{{ description }}</p>
<div class="suggestions">
<h3 class="suggestions-title">您是不是想找:</h3>
<ul class="suggestions-list">
<li
v-for="suggestion in suggestions"
:key="suggestion.id"
@click="navigateTo(suggestion.path)"
class="suggestion-item"
>
{{ suggestion.title }}
<span v-if="suggestion.category" class="suggestion-category">
{{ suggestion.category }}
</span>
</li>
</ul>
</div>
</div>
</template>
<template #actions>
<div class="smart-actions">
<BaseButton variant="primary" @click="goHome">
返回首页
</BaseButton>
<BaseButton variant="outline" @click="goBack">
返回上一页
</BaseButton>
<BaseButton
v-if="canReport"
variant="ghost"
@click="reportError"
>
报告问题
</BaseButton>
</div>
</template>
</Base404>
</template>
<script>
import { ref, onMounted, computed } from 'vue'
import { useRoute } from 'vue-router'
import Base404 from './Base404.vue'
import Animated404Illustration from './illustrations/Animated404Illustration.vue'
export default {
name: 'Smart404',
components: {
Base404,
Animated404Illustration
},
props: {
variant: {
type: String,
default: 'default'
}
},
setup(props, { emit }) {
const route = useRoute()
const suggestions = ref([])
const isLoading = ref(false)
const originalPath = computed(() =>
route.query.originalPath || route.params.pathMatch?.[0] || ''
)
const title = computed(() => {
if (originalPath.value.includes('/products/')) {
return '商品未找到'
} else if (originalPath.value.includes('/users/')) {
return '用户不存在'
}
return '页面不存在'
})
const description = computed(() => {
if (originalPath.value.includes('/products/')) {
return '您查找的商品可能已下架或不存在。'
}
return '抱歉,您访问的页面可能已被删除或暂时不可用。'
})
const canReport = computed(() => {
// 允许用户报告内部链接错误
return originalPath.value.startsWith('/') &&
!originalPath.value.includes('//')
})
onMounted(async () => {
isLoading.value = true
try {
// 根据访问路径获取智能建议
suggestions.value = await fetchSuggestions(originalPath.value)
} catch (error) {
console.error('Failed to fetch suggestions:', error)
} finally {
isLoading.value = false
}
// 发送分析事件
emit('page-not-found', {
path: originalPath.value,
referrer: document.referrer,
suggestionsCount: suggestions.value.length
})
})
const fetchSuggestions = async (path) => {
// 模拟API调用
return new Promise(resolve => {
setTimeout(() => {
const mockSuggestions = [
{ id: 1, title: '热门商品推荐', path: '/products', category: '商品' },
{ id: 2, title: '用户帮助中心', path: '/help', category: '帮助' },
{ id: 3, title: '最新活动', path: '/promotions', category: '活动' }
]
resolve(mockSuggestions)
}, 500)
})
}
const navigateTo = (path) => {
emit('suggestion-click', path)
window.location.href = path
}
const reportError = () => {
emit('report-error', {
path: originalPath.value,
timestamp: new Date().toISOString()
})
// 显示反馈表单
showFeedbackForm()
}
const goHome = () => emit('go-home')
const goBack = () => emit('go-back')
return {
suggestions,
isLoading,
originalPath,
title,
description,
canReport,
navigateTo,
reportError,
goHome,
goBack
}
}
}
</script>
五、最佳实践总结
5.1 配置检查清单
// router/config-validation.js
export function validateRouterConfig(router) {
const warnings = []
const errors = []
const routes = router.getRoutes()
// 检查是否有404路由
const has404Route = routes.some(route =>
route.path === '/:pathMatch(.*)*' || route.path === '*'
)
if (!has404Route) {
errors.push('缺少404路由配置')
}
// 检查404路由是否在最后
const lastRoute = routes[routes.length - 1]
if (!lastRoute.path.includes('(.*)') && lastRoute.path !== '*') {
warnings.push('404路由应该放在路由配置的最后')
}
// 检查是否有重复的路由路径
const pathCounts = {}
routes.forEach(route => {
if (route.path) {
pathCounts[route.path] = (pathCounts[route.path] || 0) + 1
}
})
Object.entries(pathCounts).forEach(([path, count]) => {
if (count > 1 && !path.includes(':')) {
warnings.push(`发现重复的路由路径: ${path}`)
}
})
return { warnings, errors }
}
5.2 性能优化建议
// 404页面懒加载优化
const routes = [
// 其他路由...
{
path: '/404',
component: () => import(
/* webpackChunkName: "error-pages" */
/* webpackPrefetch: true */
'@/views/errors/NotFound.vue'
)
},
{
path: '/:pathMatch(.*)*',
component: () => import(
/* webpackChunkName: "error-pages" */
'@/views/errors/CatchAllNotFound.vue'
)
}
]
// 或者使用动态导入函数
function lazyLoadErrorPage(type = '404') {
return () => import(`@/views/errors/${type}.vue`)
}
5.3 国际化和多语言支持
<!-- 多语言404页面 -->
<template>
<div class="not-found">
<h1>{{ $t('errors.404.title') }}</h1>
<p>{{ $t('errors.404.description') }}</p>
<!-- 根据语言显示不同的帮助内容 -->
<div class="localized-help">
<h3>{{ $t('errors.404.help.title') }}</h3>
<ul>
<li v-for="tip in localizedTips" :key="tip">
{{ tip }}
</li>
</ul>
</div>
</div>
</template>
<script>
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
export default {
name: 'LocalizedNotFound',
setup() {
const { locale, t } = useI18n()
const localizedTips = computed(() => {
const tips = {
'en': ['Check the URL', 'Use search', 'Visit homepage'],
'zh': ['检查网址', '使用搜索', '访问首页'],
'ja': ['URLを確認', '検索を使う', 'ホームページへ']
}
return tips[locale.value] || tips.en
})
return {
localizedTips
}
}
}
</script>
六、常见问题与解决方案
Q1: 为什么我的404页面返回200状态码?
原因:客户端渲染的应用默认返回200,需要服务器端配合。
解决方案:
// Nuxt.js 解决方案
// nuxt.config.js
export default {
render: {
// 为404页面设置正确的状态码
ssr: true
},
router: {
// 自定义错误页面
extendRoutes(routes, resolve) {
routes.push({
name: '404',
path: '*',
component: resolve(__dirname, 'pages/404.vue')
})
}
}
}
// 在页面组件中
export default {
asyncData({ res }) {
if (res) {
res.statusCode = 404
}
return {}
},
head() {
return {
title: '404 - Page Not Found'
}
}
}
Q2: 如何测试404页面?
// tests/router/404.spec.js
import { mount } from '@vue/test-utils'
import { createRouter, createWebHistory } from 'vue-router'
import { createTestingPinia } from '@pinia/testing'
import NotFound from '@/views/NotFound.vue'
describe('404 Page', () => {
it('should display 404 page for unknown routes', async () => {
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
{ path: '/:pathMatch(.*)*', component: NotFound }
]
})
const wrapper = mount(NotFound, {
global: {
plugins: [router, createTestingPinia()]
}
})
// 导航到不存在的路由
await router.push('/non-existent-page')
expect(wrapper.find('.error-code').text()).toBe('404')
expect(wrapper.find('.error-title').text()).toBe('页面不存在')
})
it('should have back button functionality', async () => {
const router = createRouter({
history: createWebHistory(),
routes: [
{ path: '/', component: { template: '<div>Home</div>' } },
{ path: '/about', component: { template: '<div>About</div>' } },
{ path: '/:pathMatch(.*)*', component: NotFound }
]
})
// 模拟浏览器历史
Object.defineProperty(window, 'history', {
value: {
length: 2
}
})
const wrapper = mount(NotFound, {
global: {
plugins: [router]
}
})
// 测试返回按钮
const backButton = wrapper.find('.btn-secondary')
await backButton.trigger('click')
// 应该返回到上一页
expect(router.currentRoute.value.path).toBe('/')
})
})
总结:Vue Router 404配置的最佳实践
-
正确配置路由:使用
/:pathMatch(.*)* 作为最后的catch-all路由
-
服务器状态码:确保404页面返回正确的HTTP 404状态码
-
用户体验:提供有用的导航选项和内容建议
-
SEO优化:设置正确的meta标签,避免搜索引擎索引404页面
-
监控分析:跟踪404错误,了解用户访问路径
-
多语言支持:为国际化应用提供本地化的404页面
-
性能考虑:使用懒加载,避免影响主包大小
-
测试覆盖:确保404功能在各种场景下正常工作
记住:一个好的404页面不仅是错误处理,更是用户体验的重要组成部分。精心设计的404页面可以转化流失的用户,提供更好的品牌体验。