هل تريد بناء تطبيق Vue 3 جاهز للإنتاج؟ هذا الدليل العملي يأخذك من الصفر إلى النشر، مع شرح معمق للتقنيات خلف الكواليس، الفخاخ الشائعة، والأكواد الحقيقية التي تستخدمها الشركات الكبرى.
تخيل أنك تفتح متصفحك وتجد أن تطبيقك الجديد يعمل بسلاسة تامة، حتى مع ٥٠ ألف مستخدم متزامن. لا توجد أخطاء في الـ Console، لا توجد طلبات HTTP عالقة، والـ Memory Usage مستقر عند ٢٠٠ ميجابايت فقط. هذا ليس حلمًا، بل نتيجة لبناء صحيح لتطبيق Vue 3 منذ البداية. المشكلة أن معظم المطورين يبدأون بـ create-vue أو Vite، ثم يكتشفون بعد شهرين أن الـ Event Loop مسدود بـ setTimeout غير منظم، أو أن الـ State Management يتحول إلى كابوس مع نمو التطبيق. في هذا الدليل، لن نستخدم الأمثلة التافهة مثل عداد بسيط، بل سنبني تطبيق حقيقي مع Auth، API متكامل، وتحسينات أداء حقيقية تستخدمها شركات مثل GitLab وAlibaba.
سنتجاوز الـ Template التقليدي ونذهب مباشرة إلى ما يحدث خلف الكواليس: كيف يتعامل Vue 3 مع الـ Reactivity System داخليًا؟ لماذا يستخدم Proxy بدلاً من Object.defineProperty؟ وكيف يؤثر الـ Virtual DOM على أداء التطبيق عند استخدام v-for مع ١٠ آلاف عنصر؟ سنغطي كل هذا مع أكواد حقيقية قابلة للتشغيل، وليس مجرد شرح نظري. سنبدأ من الصفر، بدون مكتبات خارجية غير ضرورية، ثم نضيف ما نحتاجه خطوة بخطوة، تمامًا كما تفعل فرق التطوير في الشركات الكبيرة.
أول خطأ يقع فيه المطورون هو استخدام create-vue بدون فهم ما يفعله خلف الكواليس. نعم، الأداة رائعة وتوفر وقتًا، لكنها تخفي تفاصيل مهمة. على سبيل المثال، هل تعلم أن Vite يستخدم esbuild داخليًا لتحويل الكود إلى JavaScript قابل للتنفيذ في المتصفح؟ أو أن ملف index.html في مشروع Vite ليس مجرد ملف ثابت، بل يتم معالجته بواسطة Vite لتضمين الـ Entry Point الخاص بتطبيقك؟ دعنا نبني المشروع يدويًا لنفهم كل جزء.
سنبدأ بإنشاء مجلد جديد وتشغيل npm init -y. ثم سنضيف Vue 3 كاعتمادية باستخدام npm install vue@next. بعد ذلك، سننشئ ملف main.js كمدخل للتطبيق، لكن بدلاً من استخدام mount مباشرة، سنفهم كيف يعمل نظام الـ Reactivity في Vue 3. سننشئ مكونًا بسيطًا باستخدام defineComponent، لكن مع إضافة توضيحية عن كيفية تعامل Vue مع الـ Props والـ State داخليًا. سنستخدم أيضًا TypeScript منذ البداية، لأن الشركات الكبرى مثل GitLab تعتمد عليه بشكل كامل في مشاريع Vue 3 الخاصة بها.
# إعداد المشروع يدويًا بدون أدوات إنشاء جاهزة
mkdir vue3-production-app
cd vue3-production-app
npm init -y
npm install vue@next typescript @vue/compiler-sfc --save
npm install vite @vitejs/plugin-vue @types/node --save-dev
# إنشاء ملف tsconfig.json أساسي
cat > tsconfig.json <<EOF
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"strict": true,
"jsx": "preserve",
"moduleResolution": "node",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.vue"]
}
EOF
# إنشاء ملف vite.config.ts
cat > vite.config.ts <<EOF
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, './src')
}
},
server: {
port: 3000,
strictPort: true
},
build: {
outDir: 'dist',
emptyOutDir: true,
rollupOptions: {
output: {
manualChunks: {
vue: ['vue'],
vendor: ['axios', 'pinia']
}
}
}
}
})
EOFلاحظ أننا أضفنا manualChunks في إعدادات Rollup. هذا مهم لتقسيم الكود إلى chunks أصغر، مما يحسن وقت التحميل الأولي للتطبيق. معظم المطورين يتجاهلون هذا الإعداد، ثم يشتكون من أن ملف main.js أصبح بحجم ٥ ميجابايت. أيضًا، استخدمنا alias لـ @ للإشارة إلى مجلد src، وهذا معيار متبنى في معظم مشاريع Vue الكبيرة لتجنب المسارات الطويلة مثل ../../../components/Button.vue.
بعد إعداد المشروع، يأتي التحدي الأكبر: كيف تنظم الكود بحيث لا يتحول المشروع إلى فوضى بعد شهرين من التطوير؟ معظم المطورين يبدأون بمجلد components، ثم يضيفون مجلدات عشوائية مثل utils وhelpers وservices، ثم يكتشفون بعد فترة أن لديهم ٥٠ مكونًا في مجلد واحد، وبعضهم يعتمد على مجلدات فرعية مثل components/base وcomponents/ui، لكن بدون استراتيجية واضحة.
في تجربتي مع فرق التطوير في شركات مثل Souq (الآن أمازون الشرق الأوسط)، وجدنا أن أفضل بنية هي تقسيم المشروع حسب الميزة (Feature-based) وليس حسب النوع (Type-based). على سبيل المثال، بدلاً من وضع كل المكونات في مجلد components، نضع كل ما يتعلق بميزة
# بنية المشروع المقترحة (Feature-based)
src/
├── assets/
│ ├── fonts/
│ ├── images/
│ └── styles/
│ ├── _variables.scss
│ ├── _mixins.scss
│ └── main.scss
├── features/
│ ├── auth/
│ │ ├── components/
│ │ │ ├── LoginForm.vue
│ │ │ └── RegisterForm.vue
│ │ ├── composables/
│ │ │ └── useAuth.ts
│ │ ├── services/
│ │ │ └── authService.ts
│ │ ├── stores/
│ │ │ └── authStore.ts
│ │ ├── types/
│ │ │ └── authTypes.ts
│ │ └── index.ts
│ ├── dashboard/
│ │ ├── components/
│ │ │ ├── StatsCard.vue
│ │ │ └── RecentActivity.vue
│ │ ├── composables/
│ │ │ └── useDashboard.ts
│ │ └── ...
├── shared/
│ ├── components/
│ │ ├── BaseButton.vue
│ │ ├── BaseInput.vue
│ │ └── BaseModal.vue
│ ├── composables/
│ │ └── useApi.ts
│ ├── directives/
│ │ └── v-focus.ts
│ ├── plugins/
│ │ └── i18n.ts
│ └── utils/
│ ├── dateUtils.ts
│ └── validationUtils.ts
├── App.vue
└── main.tsهذه البنية تسمح بتطوير الميزات بشكل مستقل، وتجعل من السهل على المطورين الجدد فهم تدفق البيانات داخل كل ميزة. على سبيل المثال، كل ما يتعلق بالمصادقة موجود في مجلد auth، بما في ذلك المكونات، الـ Composables، الـ Services، وحتى الـ Store الخاص بها. هذا يقلل من الوقت الذي يضيع في البحث عن الملفات ذات الصلة عندما تريد تعديل شيء ما.
لاحظ أننا استخدمنا مجلد shared للمكونات المشتركة مثل الأزرار والحقول النصية. هذه المكونات يجب أن تكون عامة جدًا ولا تعتمد على منطق معين. أيضًا، أضفنا مجلد composables داخل كل ميزة، وهذا مهم لفصل منطق العمل عن المكونات نفسها، مما يجعل الكود أكثر قابلية للاختبار وإعادة الاستخدام.
إذا كنت لا تزال تستخدم Vuex في عام ٢٠٢٤، فأنت تخسر الكثير. Pinia هي الخيار الرسمي الجديد لـ Vue، وهي أسهل في الاستخدام وأكثر مرونة. الفرق الرئيسي بين Vuex وPinia هو أن Vuex يعتمد على مفهوم واحد مركزي للـ Store، بينما Pinia يسمح بإنشاء متاجر متعددة ومستقلة. هذا يجعل Pinia أكثر ملاءمة للتطبيقات الكبيرة التي تحتوي على عشرات الميزات المختلفة.
دعنا نبني متجر Pinia لميزة المصادقة. سنستخدم TypeScript منذ البداية، لأن Pinia تدعمه بشكل ممتاز. سننشئ متجرًا يحتوي على state، actions، وgetters، مع إضافة توضيح عن كيفية تعامل Pinia مع الـ Reactivity داخليًا. هل تعلم أن Pinia تستخدم نفس نظام الـ Reactivity الخاص بـ Vue 3، مما يعني أنها أسرع وأكثر كفاءة من Vuex؟
// src/features/auth/stores/authStore.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { useApi } from '@/shared/composables/useApi'
import type { User } from '@/features/auth/types/authTypes'
interface AuthState {
user: User | null
token: string | null
isAuthenticated: boolean
loading: boolean
error: string | null
}
export const useAuthStore = defineStore('auth', () => {
const api = useApi()
const state = ref<AuthState>({
user: null,
token: localStorage.getItem('token') || null,
isAuthenticated: false,
loading: false,
error: null
})
// Getters
const getUser = computed(() => state.value.user)
const isAuth = computed(() => state.value.isAuthenticated)
const getToken = computed(() => state.value.token)
const getError = computed(() => state.value.error)
// Actions
const login = async (email: string, password: string) => {
state.value.loading = true
state.value.error = null
try {
const resp await api.post('/auth/login', { email, password })
state.value.user = response.data.user
state.value.token = response.data.token
state.value.isAuthenticated = true
localStorage.setItem('token', response.data.token)
// تحديث الـ Axios headers تلقائيًا
api.setAuthToken(response.data.token)
} catch (err) {
state.value.error = err.response?.data?.message || 'فشل تسجيل الدخول'
throw err
} finally {
state.value.loading = false
}
}
const logout = () => {
state.value.user = null
state.value.token = null
state.value.isAuthenticated = false
localStorage.removeItem('token')
api.setAuthToken(null)
}
const checkAuth = async () => {
if (!state.value.token) return false
try {
const response = await api.get('/auth/me')
state.value.user = response.data.user
state.value.isAuthenticated = true
return true
} catch {
logout()
return false
}
}
return {
state,
getUser,
isAuth,
getToken,
getError,
login,
logout,
checkAuth
}
})لاحظ أننا استخدمنا ref بدلاً من reactive داخل المتجر. هذا لأن ref أسهل في التعامل مع Typescript، ويمكننا استخدامه مباشرة في الـ Template دون الحاجة إلى toRefs. أيضًا، أضفنا منطقًا للتحقق من حالة المصادقة عند تحميل التطبيق باستخدام checkAuth، وهذا مهم لتجنب حالة الـ Race Condition حيث قد يحاول التطبيق جلب بيانات المستخدم قبل التحقق من وجود token صالح.
من تجربتي، أحد أكبر الأخطاء التي يقع فيها المطورون هو عدم فصل منطق الـ API عن المتجر. في الكود أعلاه، استخدمنا useApi كـ Composable منفصل، مما يجعل المتجر أكثر قابلية للاختبار ويمكننا استبدال الـ API بسهولة إذا احتجنا إلى ذلك. أيضًا، لاحظنا أننا نخزن الـ token في localStorage، وهذا ليس آمنًا تمامًا، لكننا سنعالجه لاحقًا باستخدام HttpOnly Cookies.
معظم المطورين يستخدمون Axios دون فهم كيف يتعامل مع الـ Requests داخليًا. المشكلة أن Axios يستخدم XMLHttpRequest في المتصفحات القديمة وFetch في الحديثة، وهذا قد يسبب مشاكل في إدارة الـ Requests عند إلغاء المكونات. على سبيل المثال، إذا قمت بإرسال طلب HTTP داخل مكون، ثم قمت بإلغاء المكون قبل انتهاء الطلب، قد يحدث تسرب للذاكرة لأن الـ Request لا يزال قيد التنفيذ.
سننشئ Composable مخصص لـ API باستخدام Axios، مع إضافة منطق لإلغاء الطلبات عند إلغاء المكونات. سنستخدم أيضًا TypeScript لضمان نوعية البيانات التي نرسلها ونستقبلها. هذا مهم بشكل خاص في التطبيقات الكبيرة حيث قد يكون لديك عشرات الـ Endpoints المختلفة.
// src/shared/composables/useApi.ts
import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse } from 'axios'
import { ref, type Ref } from 'vue'
interface ApiResponse<T> {
data: T
status: number
statusText: string
headers: any
config: AxiosRequestConfig
request?: any
}
interface ApiError extends Error {
response?: {
data: any
status: number
headers: any
}
request?: any
config: AxiosRequestConfig
}
export function useApi(baseURL: string = import.meta.env.VITE_API_BASE_URL) {
const api: AxiosInstance = axios.create({
baseURL,
timeout: 10000,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
})
// إضافة الـ Interceptor لإضافة الـ token إلى كل طلب
api.interceptors.request.use((config) => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// إضافة الـ Interceptor لمعالجة الأخطاء العامة
api.interceptors.response.use(
(response) => response,
(error: ApiError) => {
if (error.response?.status === 401) {
// إعادة توجيه إلى صفحة تسجيل الدخول إذا كان الـ token غير صالح
window.location.href = '/login'
}
return Promise.reject(error)
}
)
const setAuthToken = (token: string | null) => {
if (token) {
api.defaults.headers.common['Authorization'] = `Bearer ${token}`
} else {
delete api.defaults.headers.common['Authorization']
}
}
const request = <T>(config: AxiosRequestConfig): Promise<ApiResponse<T>> => {
const source = axios.CancelToken.source()
const requestConfig: AxiosRequestC {
...config,
cancelToken: source.token
}
const response: Ref<ApiResponse<T> | null> = ref(null)
const error: Ref<ApiError | null> = ref(null)
const loading: Ref<boolean> = ref(true)
api.request(requestConfig)
.then((res: AxiosResponse<T>) => {
response.value = {
data: res.data,
status: res.status,
statusText: res.statusText,
headers: res.headers,
config: res.config,
request: res.request
}
})
.catch((err: ApiError) => {
error.value = err
})
.finally(() => {
loading.value = false
})
return {
response,
error,
loading,
cancel: () => source.cancel('Request canceled by user')
}
}
return {
api,
setAuthToken,
get: <T>(url: string, config?: AxiosRequestConfig) => request<T>({ method: 'get', url, ...config }),
post: <T>(url: string, data?: any, config?: AxiosRequestConfig) => request<T>({ method: 'post', url, data, ...config }),
put: <T>(url: string, data?: any, config?: AxiosRequestConfig) => request<T>({ method: 'put', url, data, ...config }),
delete: <T>(url: string, config?: AxiosRequestConfig) => request<T>({ method: 'delete', url, ...config })
}
}لاحظ أننا أضفنا CancelToken إلى كل طلب. هذا مهم لإلغاء الطلبات عند إلغاء المكونات، مما يمنع الـ Memory Leaks. أيضًا، استخدمنا TypeScript لضمان أن البيانات التي نستقبلها من الـ API تطابق النوع المتوقع. على سبيل المثال، عندما نستخدم get<User>، نتأكد أن البيانات المستلمة هي من نوع User.
من الأخطاء الشائعة التي يقع فيها المطورون هو عدم معالجة الأخطاء بشكل صحيح. في الكود أعلاه، أضفنا Interceptor لمعالجة الأخطاء العامة مثل الـ 401 Unauthorized. هذا يضمن أن المستخدم يتم إعادة توجيهه إلى صفحة تسجيل الدخول إذا انتهت صلاحية الـ token، بدلاً من رؤية أخطاء غير مفهومة.
إذا كنت تعتقد أن تطبيق Vue 3 سريع بشكل افتراضي، فأنت مخطئ. نعم، Vue 3 أسرع من Vue 2، لكنه ليس مثاليًا. هناك العديد من الفخاخ التي يمكن أن تجعل تطبيقك بطيئًا بشكل ملحوظ. على سبيل المثال، استخدام v-for مع قائمة كبيرة بدون key فريد، أو تحميل جميع المكونات في البداية بدلاً من التحميل الكسول، أو عدم استخدام الـ Virtual Scrolling للقوائم الطويلة.
دعنا نبدأ بأهم تحسين: التحميل الكسول للمكونات. في التطبيقات الكبيرة، قد يكون لديك عشرات المكونات التي لا يحتاج المستخدم إلى تحميلها فورًا. على سبيل المثال، مكونات لوحة التحكم لا يجب تحميلها إلا بعد تسجيل الدخول. يمكننا استخدام defineAsyncComponent مع Suspense لتحقيق ذلك.
// src/router/index.ts
import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'
import { useAuthStore } from '@/features/auth/stores/authStore'
const routes: RouteRecordRaw[] = [
{
path: '/',
name: 'home',
component: () => import('@/features/home/HomePage.vue'),
meta: { requiresAuth: false }
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import('@/features/dashboard/DashboardPage.vue'),
meta: { requiresAuth: true }
},
{
path: '/login',
name: 'login',
component: () => import('@/features/auth/components/LoginForm.vue'),
meta: { requiresAuth: false }
}
]
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes
})
// إضافة الـ Navigation Guard
router.beforeEach(async (to, from, next) => {
const authStore = useAuthStore()
const isAuthenticated = await authStore.checkAuth()
if (to.meta.requiresAuth && !isAuthenticated) {
next({ name: 'login' })
} else if (to.name === 'login' && isAuthenticated) {
next({ name: 'dashboard' })
} else {
next()
}
})
export default routerلاحظ أننا استخدمنا () => import لتحميل المكونات بشكل كسول. هذا يعني أن المكونات لن تُحمل إلا عند الحاجة إليها، مما يقلل من حجم الحزمة الأولية ويحسن وقت التحميل. أيضًا، أضفنا Navigation Guard للتحقق من حالة المصادقة قبل السماح للمستخدم بالوصول إلى صفحات معينة.
تحسين آخر مهم هو استخدام الـ Virtual Scrolling للقوائم الطويلة. إذا كان لديك قائمة تحتوي على آلاف العناصر، فإن استخدام v-for مباشرة سيجعل التطبيق بطيئًا للغاية. بدلاً من ذلك، يمكننا استخدام مكتبة مثل vue-virtual-scroller لعرض العناصر المرئية فقط. إليك مثال على كيفية استخدامها:
<template>
<div class="virtual-list-container">
<RecycleScroller
class="scroller"
:items="items"
:item-size="50"
key-field="id"
v-slot="{ item }"
>
<div class="item">
{{ item.name }}
</div>
</RecycleScroller>
</div>
</template>
setup lang="ts">
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
interface Item {
id: number
name: string
}
const items: Item[] = Array.from({ length: 10000 }, (_, i) => ({
id: i,
name: `Item ${i}`
}))
</script>
<style scoped>
.virtual-list-container {
height: 500px;
overflow: hidden;
}
.scroller {
height: 100%;
}
.item {
height: 50px;
padding: 10px;
border-bottom: 1px solid #eee;
}
</style>هذا المكون يعرض فقط العناصر المرئية في الـ Viewport، مما يقلل عدد العناصر التي يتم عرضها بشكل كبير. على سبيل المثال، إذا كان ارتفاع العنصر ٥٠ بكسل وارتفاع الحاوية ٥٠٠ بكسل، فسيتم عرض ١٠ عناصر فقط بدلاً من ١٠ آلاف، مما يحسن الأداء بشكل كبير.
تحسين آخر مهم هو استخدام memoization لتجنب إعادة حساب القيم المعقدة. على سبيل المثال، إذا كان لديك computed property معقدة، يمكنك استخدام useMemo من VueUse لتجنب إعادة حسابها في كل مرة يتغير الـ State. إليك مثال:
setup lang="ts">
import { computed } from 'vue'
import { useMemo } from 'vue-use'
const items = ref([...]) // قائمة كبيرة من العناصر
// بدون memoization
const filteredItems = computed(() => {
return items.value.filter(item => item.isActive).sort((a, b) => b.score - a.score)
})
// مع memoization
const memoizedFilteredItems = useMemo(() => {
return items.value.filter(item => item.isActive).sort((a, b) => b.score - a.score)
}, [items.value])
</script>بعد بناء التطبيق، يأتي التحدي الأكبر: نشره للإنتاج. معظم المطورين يستخدمون npm run build ثم يرفعون الملفات إلى سيرفر، لكن هذا ليس كافيًا. هناك العديد من التفاصيل التي يجب مراعاتها لضمان أن التطبيق يعمل بشكل صحيح وآمن في بيئة الإنتاج.
أولاً، يجب ضبط إعدادات Vite بشكل صحيح للإنتاج. على سبيل المثال، يجب تمكين ضغط الملفات باستخدام gzip أو brotli، وتقسيم الكود إلى chunks أصغر، وتمكين source maps للإنتاج (مع الحذر من كشف الكود). إليك إعدادات Vite الموصى بها للإنتاج:
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import { visualizer } from 'rollup-plugin-visualizer'
import compression from 'vite-plugin-compression'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
// ضغط الملفات باستخدام gzip
compression({
algorithm: 'gzip',
ext: '.gz'
}),
// إنشاء تقرير عن حجم الحزمة
visualizer({
open: true,
gzipSize: true,
brotliSize: true
})
],
resolve: {
alias: {
'@': resolve(__dirname, './src')
}
},
build: {
outDir: 'dist',
emptyOutDir: true,
// تقسيم الكود إلى chunks أصغر
rollupOptions: {
output: {
manualChunks: {
vue: ['vue'],
pinia: ['pinia'],
vendor: ['axios', 'vue-router', 'vue-virtual-scroller']
}
}
},
// تمكين source maps للإنتاج (مع الحذر)
sourcemap: true,
// تمكين minification
minify: 'terser',
terserOptions: {
compress: {
drop_console: true, // إزالة console.log في الإنتاج
drop_debugger: true
}
}
}
})لاحظ أننا أضفنا compression لضغط الملفات باستخدام gzip، وهذا يقلل حجم الملفات بشكل كبير. أيضًا، استخدمنا visualizer لإنشاء تقرير عن حجم الحزمة، مما يساعد في تحديد الملفات الكبيرة التي قد تحتاج إلى تحسين. بالإضافة إلى ذلك، قمنا بإزالة console.log وdebugger من كود الإنتاج باستخدام terserOptions.
بعد بناء التطبيق، يجب نشره على سيرفر يدعم HTTP/2 وHTTPS. HTTP/2 يحسن أداء التحميل عن طريق السماح بتحميل ملفات متعددة في نفس الوقت عبر اتصال واحد. HTTPS ضروري للأمان، خاصة إذا كان التطبيق يتعامل مع بيانات حساسة. يمكنك استخدام خدمات مثل Vercel أو Netlify للنشر بسهولة، أو نشره على سيرفر خاص باستخدام Nginx.
إذا اخترت استخدام Nginx، إليك إعدادات موصى بها لتطبيق Vue 3:
server {
listen 443 ssl http2;
server_name yourdomain.com;
ssl_certificate /path/to/your/certificate.pem;
ssl_certificate_key /path/to/your/private-key.pem;
root /var/www/your-vue-app/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
gzip_static on;
brotli_static on;
expires 1y;
add_header Cache-Control "public";
}
location /api {
proxy_pass http://your-backend-server;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}لاحظ أننا استخدمنا try_files لتوجيه جميع الطلبات إلى index.html، وهذا مهم لتطبيقات الـ Single Page Application. أيضًا، قمنا بتمكين gzip_static وbrotli_static لاستخدام الملفات المضغوطة التي تم إنشاؤها بواسطة Vite. بالإضافة إلى ذلك، أضفنا Cache-Control لتخزين الملفات الثابتة في ذاكرة التخزين المؤقت للمتصفح لمدة عام كامل.
بعد كل هذه التفاصيل، إليك خلاصة ما تعلمناه في سطرين فقط: استخدم Pinia لإدارة الـ State، وقسم تطبيقك حسب الميزات وليس حسب النوع، واستخدم التحميل الكسول للمكونات، وتأكد من إلغاء الطلبات HTTP عند إلغاء المكونات لمنع الـ Memory Leaks. إذا اتبعت هذه النصائح، فستبني تطبيقًا سريعًا ومستقرًا وجاهزًا للإنتاج، حتى مع عشرات الآلاف من المستخدمين المتزامنين.
تذكر أن بناء تطبيق للإنتاج ليس مجرد كتابة كود يعمل، بل هو كتابة كود قابل للصيانة، وسريع، وآمن. لا تكتفِ بالحلول السهلة، بل افهم ما يحدث خلف الكواليس واستخدم الأدوات المناسبة لكل مهمة. Vue 3 أداة قوية، لكنها ليست سحرية، وتعتمد بشكل كبير على كيفية استخدامها.