器全攻略:Nginx配置與常見問題排查)
部署Vue項目這件事說難不難說簡單也真有不少坑。我在本地開發(fā)環(huán)境把前端項目跑得飛起結(jié)果第一次真正打包上傳到Linux服務(wù)器時白屏、404、資源加載不出來折騰了一晚上才把問題理清楚。后來部署的項目多了總結(jié)出一套固定的流程和排查思路基本不會再被這類問題卡住。這篇文章就圍繞“Vue項目打包并部署到Linux服務(wù)器”這條主線把從環(huán)境準備、打包配置、服務(wù)器部署到常見問題排查的完整過程都梳理一遍希望能幫正準備自己搞定部署的同學少走幾步彎路。這篇文章適合誰來看呢主要是這幾類人剛把Vue項目寫完、想自己發(fā)布上線的前端開發(fā)者公司里需要獨立承擔前后端部署任務(wù)的“全干工程師”以及想搞清楚Nginx到底怎么配置、為什么打包后白屏的新手。讀完你至少能獲得一套可以直接照著做的部署流程以及幾個99%會遇到的問題的解決方案。1. 部署前的基本功理清思路再做也不遲1.1 前端部署的本質(zhì)是什么很多同學第一次接觸部署時容易把這件事想得太玄乎其實前端部署的本質(zhì)非常簡單把構(gòu)建后的靜態(tài)資源文件放到一臺能通過公網(wǎng)訪問的服務(wù)器上再讓服務(wù)器軟件如Nginx把這些文件正確地提供給訪問者。Vue項目在開發(fā)時是通過Node.js啟動一個開發(fā)服務(wù)器由它來編譯組件、熱更新模塊。但開發(fā)服務(wù)器只適合開發(fā)階段性能、穩(wěn)定性都不適合線上環(huán)境。所以部署的第一步永遠是執(zhí)行打包命令比如npm run build把Vue的源碼編譯成純靜態(tài)的HTML、CSS和JavaScript文件。這些文件被放在dist目錄里就是你部署時要上傳的全部內(nèi)容。我遇到過不少同事部署時直接把整個項目源碼拷到服務(wù)器上還問我為什么訪問不了。原理上說源碼中包含.vue文件、node_modules依賴等瀏覽器根本不認識這些格式。瀏覽器能識別的只有構(gòu)建后的JavaScript、CSS、HTML。理解了這一點部署的思路就清晰了把dist里的東西搬到服務(wù)器的Web目錄然后配置好入口文件和路由轉(zhuǎn)發(fā)規(guī)則。1.2 需要用到的基礎(chǔ)工具和Linux環(huán)境部署本質(zhì)上就是文件傳輸和進程管理所以得先準備好一套工具鏈。我自己常用的組合是本地終端工具Windows推薦用FinalShell或Xshell方便可視化查看文件、執(zhí)行命令macOS和Linux直接用系統(tǒng)自帶的終端就行。服務(wù)器系統(tǒng)本文以Linux Ubuntu/Debian系列的操作為例CentOS系列只是包管理器命令不同yum替換apt邏輯完全一致。Web服務(wù)器軟件Nginx目前前端部署的絕對主流選擇。性能好、配置直觀、反向代理功能強大幾乎沒有不選它的理由。連接Linux服務(wù)器后有幾條高頻命令你得順手比如cd進入目錄、ls查看文件、pwd查看當前路徑、mv移動文件。上傳文件可以用scp命令但圖形化工具更直觀我通常直接用FinalShell自帶的文件管理器把dist目錄拖進去效率很高。1.3 部署方案選型Nginx作為首選的原因也許你會問為什么不直接用Node.js寫個服務(wù)去托管靜態(tài)文件理論上可以但實際生產(chǎn)環(huán)境中Nginx處理靜態(tài)文件的效率遠高于Node.js而且Nginx還可以統(tǒng)一負責HTTPS證書配置、域名綁定、反向代理、負載均衡和Gzip壓縮。這些功能如果用Node.js自己實現(xiàn)要寫不少代碼維護成本還不低。更關(guān)鍵的是當前端項目需要請求后端API時會產(chǎn)生跨域問題。開發(fā)環(huán)境下Vue腳手架幫你配置了proxy代理但生產(chǎn)環(huán)境沒有這個能力。Nginx一個location塊就能解決跨域轉(zhuǎn)發(fā)這也是它能成為前端部署標配的核心原因。2. 打包前必須檢查的3個關(guān)鍵配置2.1 路由mode決定刷新是否404Vue Router有兩種路由模式——hash和history這是打包前首先要確認的一個點。開發(fā)環(huán)境下很多人習慣使用history模式因為URL看起來更干凈比如http://example.com/home沒有煩人的#號。開發(fā)服務(wù)器的原理會幫你在訪問任意路徑時都回退到index.html所以一切正常。但部署到Nginx后問題就出現(xiàn)了。你用http://example.com/home訪問首頁然后點擊頁面內(nèi)跳轉(zhuǎn)沒問題因為Vue Router在內(nèi)存中切換路由不發(fā)送實際請求。可是如果你在瀏覽器地址欄直接刷新這個URL或者分享給別人后對方直接點開Nginx會去磁盤上查找/home這個文件或目錄找不到自然返回404。解決這個問題需要給Nginx加一條try_files回退規(guī)則后面實操部分會詳細說。如果你們項目兼容性好、不想處理這個麻煩直接用hash模式最省心部署后永遠不會有刷新404的問題代價是URL里多個#。2.2 publicPath決定靜態(tài)資源能否加載publicPath決定了打包后的JavaScript、CSS、圖片等靜態(tài)資源的引用路徑是部署中出錯率最高的配置之一。構(gòu)建工具的默認配置方案是publicPath: /也就是資源路徑從網(wǎng)站根目錄開始引用。如果項目恰好部署在域名根路徑如http://example.com/那就沒問題。但如果部署在子路徑下如http://example.com/myapp/必須把publicPath改成/myapp/否則打包后的HTML引用的JS、CSS路徑都指向根路徑瀏覽器請求404。這里有個實際案例可以講清楚我之前在項目里把publicPath設(shè)成./相對路徑以為這樣更通用。結(jié)果發(fā)現(xiàn)如果是多級路由如/home/detail相對路徑會解析成/home/detail/開頭資源依然加載失敗。所以我的建議是子路徑部署時直接用絕對路徑比如/myapp/別用相對路徑偷懶。2.3 接口環(huán)境變量生產(chǎn)環(huán)境API地址如果項目代碼里寫死了接口地址http://localhost:8080/api打包后部署到服務(wù)器前端頁面在用戶瀏覽器里運行請求的卻是用戶電腦上的localhost這必然失敗。正確的做法是使用環(huán)境變量區(qū)分開發(fā)環(huán)境和生產(chǎn)環(huán)境。Vue項目里創(chuàng)建.env.production文件里面設(shè)置VUE_APP_BASE_URL/api或者完整的線上域名然后在代碼里統(tǒng)一通過process.env.VUE_APP_BASE_URL訪問。這樣打包時構(gòu)建工具自動讀取生產(chǎn)環(huán)境變量代碼里的請求地址就會指向線上API地址。如果你的后端接口和前端部署在同一個Nginx上/api開頭的請求交給Nginx反向代理轉(zhuǎn)發(fā)到后端服務(wù)即可。3. 實操過程從打包到線上訪問的完整演示3.1 本地打包構(gòu)建npm run build的產(chǎn)物在項目根目錄執(zhí)行打包命令npm run build這條命令實際執(zhí)行的是vue-cli-service build最終會在根目錄生成dist文件夾。打包完成后建議先看一眼dist目錄結(jié)構(gòu)確認存在index.html和static或assets子目錄里面是壓縮后的JS和CSS文件。如果dist里空空如也說明打包有問題翻翻終端輸出排查。有一點值得注意dist目錄如果存在上一次的構(gòu)建產(chǎn)物新的打包會在部分版本中直接覆蓋但如果有“干凈的構(gòu)建”潔癖可以先執(zhí)行rm -rf dist再打包確保沒有歷史殘留文件。3.2 上傳dist到Linux服務(wù)器的兩種常用方式方式一使用scp命令適合macOS/Linux本地環(huán)境scp -r ./dist rootyour_server_ip:/var/www/myapp這個命令把本地dist目錄遞歸上傳到服務(wù)器的/var/www/myapp目錄。首次連接會提示確認指紋輸yes然后輸入密碼即可。注意目標目錄需要提前創(chuàng)建好mkdir -p /var/www/myapp。方式二使用圖形化SFTP工具適合Windows用戶FinalShell或WinSCP這類工具更直觀打開軟件新建連接填服務(wù)器IP、用戶名如root、密碼連接成功后左側(cè)是本地文件右側(cè)是服務(wù)器文件系統(tǒng)。把dist文件夾直接拖到右側(cè)目標目錄即可。第一次上傳文件較多時可能會慢一點看到進度條走完就成功了。上傳完可以在服務(wù)器上執(zhí)行l(wèi)s /var/www/myapp確認文件都在尤其檢查index.html存在。3.3 安裝并啟動Nginx服務(wù)如果服務(wù)器還沒裝Nginx先裝好# Ubuntu/Debian系統(tǒng) sudo apt update sudo apt install nginx -y # CentOS/RHEL系統(tǒng) sudo yum install nginx -y安裝完成后輸入nginx -v檢查版本號。然后啟動服務(wù)sudo systemctl start nginx sudo systemctl enable nginx第二條命令設(shè)置開機自啟防止服務(wù)器重啟后Nginx沒起來。啟動完成后用瀏覽器訪問http://服務(wù)器IP如果看到Nginx默認歡迎頁說明安裝成功。為了驗證Nginx是否正常運行可以順手執(zhí)行systemctl status nginx看到active (running)就是正常狀態(tài)。3.4 編寫Nginx配置最核心的部署步驟這是整個部署過程的核心。Nginx的默認站點配置文件在/etc/nginx/sites-available/default實操中我們通常新建一個專屬配置文件便于管理多個項目。我的建議直接在/etc/nginx/conf.d/下創(chuàng)建一個配置文件比如myapp.confserver { listen 80; server_name your_domain.com; # 換成你的域名或服務(wù)器IP root /var/www/myapp; # dist上傳后的目錄 index index.html; location / { try_files $uri $uri/ /index.html; } # 可選靜態(tài)資源緩存策略 location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { expires 7d; add_header Cache-Control public, no-transform; } }這塊配置需要逐行解釋清楚因為它承載了整個部署的核心邏輯root指定了網(wǎng)站的根目錄為/var/www/myapp當用戶訪問http://你的域名/時Nginx到這個目錄下找文件。index index.html;讓訪問目錄時自動加載index.html。try_files $uri $uri/ /index.html;這條是history模式路由部署的關(guān)鍵保障。它的邏輯是先嘗試按實際請求路徑找文件$uri如果找不到就嘗試找目錄$uri/還是找不到則回退到根目錄的index.html。這就保證了刷新子路由頁面時Vue Router接管頁面渲染而不是直接報404。靜態(tài)資源緩存策略用于給JS、CSS、圖片這類帶hash的文件加上7天緩存用戶二次訪問時加載更快同時由于文件名帶hash內(nèi)容更新后不受緩存干擾。配好后測試并生效nginx -t # 檢查配置語法是否正確 nginx -s reload # 重新加載配置配置無誤的話nginx -t會輸出syntax is ok和test is successful。然后訪問服務(wù)器IP或域名就能看到項目首頁了。3.5 配置反向代理解決前后端API跨域前端頁面部署好后接口請求如果和后端不在同一個域名下會有跨域問題。用Nginx反向代理可以優(yōu)雅地解決前端直接請求同源地址Nginx把特定前綴的請求轉(zhuǎn)發(fā)給后端服務(wù)。假設(shè)后端API服務(wù)運行在服務(wù)器的8080端口那在之前的server塊里增加一個location路徑配置location /api/ { proxy_pass http://127.0.0.1:8080/; 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; }這樣配置后前端請求/api/login時Nginx會把它轉(zhuǎn)發(fā)到http://127.0.0.1:8080/login。這里有個小坑我踩過proxy_pass http://127.0.0.1:8080/;結(jié)尾的斜杠很重要。有斜杠http://127.0.0.1:8080/匹配/api/前綴后把后面的路徑拼接到代理地址后面。即請求/api/login轉(zhuǎn)發(fā)為http://127.0.0.1:8080/login。沒有斜杠http://127.0.0.1:8080保留完整原始URI。即請求/api/login轉(zhuǎn)發(fā)為http://127.0.0.1:8080/api/login。究竟用哪種取決于后端接口是否統(tǒng)一了/api前綴。很多后端項目在Controller里設(shè)置了context-path/api那沒有斜杠的寫法才是正確的。為了讓前端代碼里的接口地址和環(huán)境變量匹配生產(chǎn)環(huán)境的VUE_APP_BASE_URL建議設(shè)置成/api。這樣前端代碼中所有請求都以/api開頭和Nginx的轉(zhuǎn)發(fā)規(guī)則完全對應(yīng)。4. 部署后出現(xiàn)問題一份高頻異常排查實錄4.1 訪問IP或域名后頁面白屏白屏是部署后最高頻的問題具體表現(xiàn)是瀏覽器打開后一片空白F12控制臺里還可能報錯。我的排查順序一般分三步。第一步看HTML源碼瀏覽器右鍵查看頁面源碼確認HTML內(nèi)容是否正確加載。如果連HTML都沒內(nèi)容問題出在Nginx配置或文件路徑上檢查root指向的目錄是否存在index.html。第二步看JS引用路徑如果HTML正常但控制臺報錯顯示某個JS文件404大概率是publicPath配置問題。打開HTML源碼查看script標簽的src屬性。如果路徑是/static/js/app.js而你的項目部署在子路徑下資源當然找不到。重新設(shè)置publicPath重新本地打包重新上傳。第三步檢查JS/CSS加載是否受緩存影響有時候代碼改了但瀏覽器還在用舊的緩存文件。部署完可以先CtrlF5強制刷新?;蛘哂肗ginx設(shè)置短緩存甚至上線時在index.html上禁用緩存保證用戶拿到最新版本。4.2 history模式下刷新某路由頁面404這個前面已經(jīng)說過原理了最典型的特征從首頁跳轉(zhuǎn)到二級頁面沒問題但地址欄直接訪問二級頁面URL就404。如果配置了try_files $uri $uri/ /index.html;問題還在那考慮是不是location /塊沒生效檢查一下是不是配置文件里存在多個server塊或location /Nginx默認優(yōu)先匹配最前面的規(guī)則如果前面的location已經(jīng)攔截了請求后面的規(guī)則不會執(zhí)行。還有一個容易忽略的情況如果項目部署在子路徑下try_files的回退地址也要調(diào)整為/子路徑/index.html例如location /myapp/ { alias /var/www/myapp/; try_files $uri $uri/ /myapp/index.html; }alias和root的路徑拼接規(guī)則不一樣這個細節(jié)經(jīng)常讓人懷疑人生。4.3 接口請求能發(fā)起但返回404或500接口404先確認Nginx的location /api/是否配置正確再看后端服務(wù)是否在監(jiān)聽預(yù)期端口??梢栽诜?wù)器上直接測試curl http://127.0.0.1:8080/api/health如果能返回數(shù)據(jù)說明后端正常問題出在Nginx代理配置上。如果返回連接拒絕檢查后端服務(wù)是否啟動監(jiān)聽端口是否正常。如果需要排查后端日志用journalctl -u 服務(wù)名或直接查看后端項目的日志文件。4.4 部署完成后頁面樣式錯亂或布局異常這個現(xiàn)象通常有兩個來源。一是上線時直接替換了dist但瀏覽器緩存了舊版本的CSS新HTML引用的還是同名CSS但內(nèi)容已經(jīng)變了最終新舊混合導致布局錯亂。解決辦法是清緩存刷新或者把Nginx的CSS緩存時間調(diào)短一些再或者確認打包后CSS文件名是否帶新的hash。二是組件里的某個圖片使用了相對路徑部署后多層路由下圖片加載失敗這種情況下通常能看到控制臺里圖片404的報錯。針對這種場景建議全局統(tǒng)一用絕對路徑配置publicPath。4.5 一臺服務(wù)器部署多個前端項目很多實際場景中一臺服務(wù)器要跑多個前端項目比如一個管理后臺、一個用戶端網(wǎng)站。這里推薦兩種方案方案一不同端口直接再新建一個server塊監(jiān)聽不同端口server { listen 8081; root /var/www/admin; index index.html; location / { try_files $uri $uri/ /index.html; } }方案二同一端口不同路徑用location配合alias做多目錄部署前面已經(jīng)提到過server { listen 80; location / { root /var/www/site; try_files $uri $uri/ /index.html; } location /admin/ { alias /var/www/admin/; try_files $uri $uri/ /admin/index.html; } }5. 進階用Docker部署的快捷路線如果不希望服務(wù)器上手動安裝Node.js、Nginx等一堆依賴用Docker方式部署前端項目也越來越流行。核心思路是本地構(gòu)建時使用Node鏡像運行時使用Nginx鏡像并通過nginx.conf把靜態(tài)資源目錄掛載進去。一個精簡版的Dockerfile可以這樣寫# 構(gòu)建階段 FROM node:18-alpine as build-stage WORKDIR /app COPY package*.json ./ RUN npm install --registryhttps://registry.npmmirror.com COPY . . RUN npm run build # 運行階段 FROM nginx:stable-alpine COPY --frombuild-stage /app/dist /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf EXPOSE 80然后在項目根目錄創(chuàng)建對應(yīng)的nginx.conf內(nèi)容和前面手動配置的server塊一致。執(zhí)行docker build -t myapp-frontend:1.0.0 . docker run -d -p 80:80 --name myapp-frontend myapp-frontend:1.0.0一條docker run就能把整個前端服務(wù)跑起來。相比手動安裝Nginx、配置環(huán)境Docker的好處是環(huán)境隔離、遷移方便團隊之間只要共享鏡像或Dockerfile別人也能快速復現(xiàn)一模一樣的部署環(huán)境。6. 部署之后的最后一步日常維護和更新項目上線后后續(xù)迭代還要繼續(xù)發(fā)新版。每次改完代碼更新的流程基本是固定的本地執(zhí)行npm run lint和測試確認沒問題。執(zhí)行npm run build生成新的dist。上傳dist到服務(wù)器覆蓋舊文件。nginx -s reload重載配置如果nginx配置沒變這一步其實可省因為靜態(tài)文件是即時生效的不需要重啟Nginx。瀏覽器強制刷新驗證。這里有個建議如果你覺得每次手動上傳太麻煩后續(xù)可以嘗試用Git鉤子或CI/CD工具如Jenkins、GitHub Actions實現(xiàn)自動構(gòu)建、自動上傳。第一次配置花點時間后面每次發(fā)版都省事。我個人的體會是前端部署這件事踩過一次坑之后后面就順了。尤其是publicPath和try_files這兩個知識點搞懂了它們基本上90%的部署問題對你來說都不是問題。最后再分享一個小技巧部署完成驗證時不妨把瀏覽器開成無痕模式訪問這樣能避開本地緩存的干擾準確判斷出到底是配置問題還是緩存問題。