別詳解:原理、配置與SEO影響)
做Web開發(fā)這幾年301和302這兩個狀態(tài)碼算是遇到頻率最高的重定向方式但我發(fā)現(xiàn)不少同學(xué)到現(xiàn)在都沒把兩者的區(qū)別完全搞明白。之前接到一個模擬項目網(wǎng)站要從HTTP整體遷到HTTPS前端同事圖省事配了一堆302跳轉(zhuǎn)結(jié)果線上訪問是沒問題搜索引擎卻不認賬舊頁面權(quán)重遲遲不轉(zhuǎn)移新關(guān)鍵詞排名一直上不來。最后定位到根因就是臨時和永久這兩個詞的語義用反了。這篇文章不寫概念教科書直接從實際工作出發(fā)把兩個狀態(tài)碼的職責邊界、服務(wù)器配置方法、踩過的坑和排查套路全部揉在一起聊。適合正在搭站的后端開發(fā)、維護線上環(huán)境的運維還有天天被SEO指標追著跑的運營同學(xué)。我會先用生活化的類比把301和302的本質(zhì)講透再給可抄作業(yè)的配置示例最后整理一份常見問題速查表??赐赀@篇文章你應(yīng)該能在五分鐘內(nèi)判斷某個場景到底該用301還是302。1. 重定向狀態(tài)碼的本質(zhì)301和302到底在告訴客戶端什么1.1 狀態(tài)碼是服務(wù)器給客戶端的一句明確旁白HTTP請求是一個問答過程瀏覽器問服務(wù)器我要訪問這個地址服務(wù)器用狀態(tài)碼回答請求收到了處理結(jié)果是這樣。狀態(tài)碼不是給用戶看的文案而是給瀏覽器、爬蟲、客戶端程序看的結(jié)構(gòu)化信息。3開頭的狀態(tài)碼統(tǒng)一表示重定向意思是你要的東西我已經(jīng)看到了但它現(xiàn)在不在這個地址我給你一個Location響應(yīng)頭里面寫著新地址你自己再跑一趟。你可以把301和302想象成導(dǎo)航App里的兩種提示。301是這個目的地已經(jīng)永久搬家了以后都用新地址來找我302是這個目的地暫時不對外開放你先去臨時地址湊合一下過段時間再回原地址試試。兩種說法看似相似但客戶端和搜索引擎處理它們的邏輯完全不同這也是很多事故的源頭。1.2 301與302的官方語義差異先看定義301 Moved Permanently請求的資源已經(jīng)被永久移動以后任何對原始URL的請求都應(yīng)該直接使用新的URL。302 Found請求的資源暫時被移到了另一個URL以后對原始URL的請求仍然應(yīng)該繼續(xù)使用原始URL。這里有個歷史彩蛋302最初的名字叫Found語義非常模糊后來HTTP/1.1規(guī)范也沒改干凈導(dǎo)致很多瀏覽器實現(xiàn)是不管301還是302只要我用GET請求跳轉(zhuǎn)后一律變成GET。所以官方規(guī)范后來補充了303 See Other和307 Temporary Redirect來解決語義混亂的問題。咱們后文會提到但工程上現(xiàn)在依然大量用到301和302所以理解其根本差異仍然是基本功。從實際影響看主要差異體現(xiàn)在三方面對比維度301 永久重定向302 臨時重定向瀏覽器緩存行為會默認緩存跳轉(zhuǎn)結(jié)果一般不會主動緩存除非響應(yīng)頭顯式允許搜索引擎處理將舊URL權(quán)重繼承到新URL索引更新為新地址繼續(xù)把舊URL視為可訪問地址不轉(zhuǎn)移權(quán)重典型觸發(fā)結(jié)果用戶再次輸入舊地址時可能直接被跳轉(zhuǎn)用戶每次都會先訪問舊地址再收到跳轉(zhuǎn)指令適用階段域名更換、結(jié)構(gòu)改版、協(xié)議升級臨時活動頁、登錄、A/B測試對普通用戶來說301和302在瀏覽器里看到的差異不大甚至感知不到。但對爬蟲和客戶端程序來說差別巨大。如果配置錯誤輕則增加無謂的請求次數(shù)重則讓網(wǎng)站整整數(shù)個月的SEO工作白干。2. 場景選型什么時候用301什么時候用3022.1 用301的典型場景凡是舊地址以后再也不打算用了的情況都該用301。我實際經(jīng)手過的場景主要有四類第一域名更換。從一個已經(jīng)運營多年的域名遷移到新域名必須把所有頁面、圖片、靜態(tài)資源的地址都通過301指到新域名。這時候如果用了302老用戶在搜索引擎里點擊舊鏈接后雖然也能到達新頁面但搜索引擎會一直覺得舊域名還有效就不會把流量和權(quán)重放下。第二HTTP協(xié)議升級到HTTPS。這是當前最普遍的場景。配置時在服務(wù)器層面把80端口的請求全部301跳轉(zhuǎn)到443端口返回帶新鏈接的Location頭。我見過有人為了圖省事直接把所有HTTP請求都用302轉(zhuǎn)發(fā)短期內(nèi)確實能跳到HTTPS頁面但搜索引擎對舊鏈接的信任度會下降而且多次臨時跳轉(zhuǎn)會讓抓取效率變差。第三URL規(guī)范化。比如一個網(wǎng)站的頁面既可以通過http://old.example.com訪問也可以通過https://old.example.com訪問搜索引擎會認為是兩個不同站點。通常選擇其中一個版本作為主版本把另一個版本通過301指過去。常見的做法是把裸域名統(tǒng)一帶www或者反過來總之要用301不能兩個域名都聲稱自己是正版。第四舊內(nèi)容永久下線但希望用戶去新的替代頁面。比如原本的/article/123改成了/posts/123舊編號以后不再復(fù)用直接301到新地址。搜索引擎會把舊頁面的外鏈聲望轉(zhuǎn)給新頁面這也是SEO遷移中最值錢的環(huán)節(jié)。我自己的經(jīng)驗是只要在需求里看到永久、以后不恢復(fù)、域名換了這些字眼第一反應(yīng)就應(yīng)該上301。猶豫不決時寧可保守一點也不要讓一個永久變更用臨時狀態(tài)碼撐半年。2.2 用302的典型場景302適合原地址仍然有效只是這次請求你要去別的地方處理的場景。最常見的有三類。登錄跳轉(zhuǎn)算一個。用戶訪問一個需要登錄的頁面系統(tǒng)會先把他帶到登錄頁登錄成功后帶著回跳參數(shù)再回到原始地址。在這個暫時走開一下的過程中原始URL本身沒有任何變化用戶下次再來還是走同樣的流程所以用302非常合適。臨時活動頁也算一個。比如節(jié)日大促原頁面欄位內(nèi)容不變只是臨時把流量引到一個活動頁活動結(jié)束后再回到原來的頁面邏輯。這里如果有緩存可能會把用戶永遠留在活動頁所以要非常注意響應(yīng)頭的緩存控制。實在拿不準時301一定不要用302配合Cache-Control: no-store是更穩(wěn)的方案。A/B測試同樣依賴302。當一個功能有兩個版本在分流實驗后臺要按用戶群體或參數(shù)決定跳到哪一版。這個跳轉(zhuǎn)對每個用戶來說可能都不一樣絕不能讓瀏覽器永久緩存某個跳轉(zhuǎn)目標否則實驗就廢了。用302的好處就在于此每次請求都老老實實到服務(wù)器問一句我這次該去哪。順帶提一句302還可以用于均衡負載。一些老舊的系統(tǒng)用302把請求輪流指到不同機房的服務(wù)節(jié)點但現(xiàn)代架構(gòu)基本都用DNS或網(wǎng)關(guān)層負載均衡了302這種方案只適合少數(shù)限制嚴格的場景。2.3 容易被忽略的303、307和308既然聊到選型就得把另外三個兄弟狀態(tài)碼拿出來說說。HTTP/1.1時代補充了303 See Other和307 Temporary Redirect后來又有了308 Permanent Redirect。303無論原請求是POST還是PUT跳轉(zhuǎn)后都讓客戶端改用GET去訪問新地址。常用于表單提交成功后跳轉(zhuǎn)到結(jié)果頁防止用戶刷新頁面導(dǎo)致重復(fù)提交。307語義和302幾乎一樣都是臨時重定向但不對請求方法做任何強制轉(zhuǎn)換。如果用POST發(fā)起請求服務(wù)器返回307瀏覽器會再用POST請求新地址。這點很重要因為很多舊實現(xiàn)的302會把POST變?yōu)镚ET導(dǎo)致請求體丟失。308語義和301一樣是永久重定向但同樣不會更改請求方法。處理非GET請求的永久遷移時用它比301更安全。工程上普通頁面的GET跳轉(zhuǎn)用301/302足夠但涉及API接口和表單回跳時要多留個心眼。我見過不少團隊把POST接口的301跳轉(zhuǎn)當GET處理導(dǎo)致客戶端拿到響應(yīng)后丟失了請求體查了半天才發(fā)現(xiàn)是狀態(tài)碼語義在作怪。3. Web服務(wù)器上的301/302配置實操3.1 主流服務(wù)器配置示例真正動手配的時候第一件事就是確定自己用的是哪種Web服務(wù)器。下面給幾份可以直接抄的例子。NginxNginx里設(shè)置重定向一般用return或rewrite。return更簡潔高效適合明確的目標地址# 整個站點的HTTP強制跳轉(zhuǎn)到HTTPS用301永久跳轉(zhuǎn) server { listen 80; server_name old.example.com; return 301 https://new.example.com$request_uri; } # 某個舊路徑永久跳轉(zhuǎn)到新路徑用301 location /old/ { return 301 https://new.example.com/new/$request_uri; } # 臨時活動頁跳轉(zhuǎn)用302 location /promo { return 302 https://new.example.com/event/spring-sale; }rewrite也能實現(xiàn)但一般在需要基于路徑正則改寫時才更值得用rewrite ^/old/(.*)$ /new/$1 permanent; # 301 rewrite ^/old/(.*)$ /new/$1 redirect; # 302ApacheApache的配置更接近人類語言可以用Redirect或RedirectMatch指令# 單個路徑的301永久重定向 Redirect 301 /old https://new.example.com/new # 基于正則匹配的302臨時重定向 RedirectMatch 302 ^/old/(.*)$ https://new.example.com/new/$1Apache配置文件改動后用apachectl graceful平滑生效Nginx用nginx -s reload不要用restart否則會有短暫中斷。Node.js / Express后端代碼里控制重定向狀態(tài)碼也很常見Express框架只需要一行const express require(express); const app express(); // 永久跳轉(zhuǎn) app.get(/old, (req, res) { res.redirect(301, /new); }); // 臨時跳轉(zhuǎn)默認就是302 app.get(/temporary, (req, res) { res.redirect(/temp); });Java Servlet / Spring BootGetMapping(/old) public void redirectOld(HttpServletResponse response) throws IOException { response.setStatus(301); response.setHeader(Location, https://new.example.com/new); response.flushBuffer(); } // 或者用Spring Boot更簡潔的方式 GetMapping(/old2) public RedirectView redirectOld2() { RedirectView view new RedirectView(/new); view.setStatusCode(HttpStatus.MOVED_PERMANENTLY); return view; }配置本身的語法不難難的是弄清楚目標地址怎么拼。3.2 配置時最容易踩的路徑坑我在代碼審查里最??吹降腻e誤有三個。第一個是丟掉查詢參數(shù)。Nginx里如果寫死return 301 https://new.example.com/new;那么用戶訪問/old?fromad時fromad就丟了。正確寫法是保留$request_uri或用對應(yīng)變量拼上return 301 https://new.example.com/new$request_uri;這里的$request_uri包含原來的URI和查詢字符串能把/old?fromad原樣帶到https://new.example.com/new?fromad。第二個是域名帶的路徑重復(fù)。假設(shè)源站是https://new.example.com/blog如果配置里不小心寫成return 301 https://new.example.com/blog$request_uri而$request_uri又包含/old結(jié)果就變成/blog/old了。正確做法是明確我要把舊路徑映射到新頁面的哪個根位置最好先寫一個測試文件驗證再上線。第三個是特殊字符和中文路徑。重定向目標URL只支持ASCII字符如果舊路徑里有中文或空格服務(wù)器端最好對Location頭做URL編碼否則部分瀏覽器會直接報地址無效。Nginx和Apache通常會自動處理一部分但后端代碼里自己拼接時一定要用encodeURI、URLEncoder.encode這類工具。修改完配置后我習(xí)慣用兩三步自檢先看是否保留參數(shù)再看目標URL是否可達最后用命令行工具確認響應(yīng)頭。別小看這三步能擋掉一大半線上事故。3.3 如何驗證重定向配置生效驗證重定向不是用瀏覽器點一下看看能不能跳這么簡單瀏覽器會默認跟隨跳轉(zhuǎn)你根本看不到中間過程。最直接的方式是用curl查看響應(yīng)頭限制不跟隨跳轉(zhuǎn)curl -I http://old.example.com/old輸出里會看到HTTP/1.1 301 Moved Permanently Location: https://new.example.com/newHTTP/1.1 301就是要的重點。如果看到的是302 Found說明你配錯了。如果想去掉緩存因素的干擾可以加-H Cache-Control: no-cache再看一遍。要查看完整跳轉(zhuǎn)鏈用curl -L并設(shè)置最大跳轉(zhuǎn)次數(shù)curl -L --max-redirs 5 http://old.example.com/old這個方法能快速發(fā)現(xiàn)跳轉(zhuǎn)鏈太長和循環(huán)重定向問題。瀏覽器開發(fā)者工具里的Network面板也能看到每次請求的狀態(tài)碼記得勾選Disable cache再測試否則瀏覽器緩存的301結(jié)果會干擾判斷。4. 重定向配置常見問題與排查方法4.1 問題重定向循環(huán)典型的報錯是瀏覽器顯示此網(wǎng)頁造成了太多重定向或ERR_TOO_MANY_REDIRECTS。原因通常是A跳B、B再跳A形成死循環(huán)。常見場景是HTTP與HTTPS兩個配置互相指舊域名指向新域名新域名又因為某個規(guī)則指回舊域名。排查思路很簡單第一步先看服務(wù)器配置確認有沒有兩個return規(guī)則同時命中同一個URL。第二步用curl逐步查看curl -I -L --max-redirs 3 http://old.example.com/如果輸出里反復(fù)出現(xiàn)HTTP/1.1 301且Location一直變不回http://基本就是循環(huán)。我踩過一次很隱蔽的坑Nginx里server塊的listen 80和listen 443 ssl都寫了return 301 https://new.example.com$request_uri看起來沒錯但其中一個證書配置失效導(dǎo)致HTTPS端口響應(yīng)失敗瀏覽器在兩次跳轉(zhuǎn)后反而報循環(huán)。后來把監(jiān)聽規(guī)則放全局變量里統(tǒng)一管理才解決。4.2 問題302跳轉(zhuǎn)后權(quán)重丟失這個問題的表現(xiàn)是頁面能訪問但搜索引擎收錄一直不更新舊關(guān)鍵詞排名波動。原因幾乎都是永久性遷移卻用了302。搜索引擎對302的處理是舊地址仍然存活先抓取舊地址臨時跳轉(zhuǎn)不完全信任所以權(quán)重不會真正轉(zhuǎn)移。我的建議是凡是判斷為永久變更立刻改成301。如果業(yè)務(wù)方說暫時不確定以后恢復(fù)不恢復(fù)怎么辦那就給出時間底線——超過兩周到一個月還沒恢復(fù)的就默認按永久遷移處理改成301。不要為了以后方便恢復(fù)而長期使用302這個舒適區(qū)會讓SEO團隊很痛苦。另外還有一層容易被忽略CDN或中間代理可能會緩存302響應(yīng)。有些緩存系統(tǒng)對于帶Set-Cookie或特定路徑的302不會緩存但純靜態(tài)路徑就說不準。如果發(fā)現(xiàn)改完配置用戶還在走舊地址優(yōu)先檢查CDN節(jié)點節(jié)點緩存和瀏覽器緩存。4.3 問題瀏覽器或CDN緩存了舊的跳轉(zhuǎn)結(jié)果301的一個副作用是瀏覽器會記住這個跳轉(zhuǎn)下次訪問舊地址直接跳到新地址。這本來是性能優(yōu)化但在調(diào)試階段非常煩人你把301改成302或改成另一個地址后瀏覽器還在用舊結(jié)果。清理辦法分三檔第一檔是開發(fā)者工具Network面板里勾選Disable cache然后強制刷新第二檔是用curl -H Cache-Control: no-cache驗證服務(wù)器實際響應(yīng)第三檔是換一個無痕窗口或換瀏覽器測試。如果是CDN緩存需要在CDN控制臺刷新對應(yīng)路徑的緩存或者在響應(yīng)頭里加Cache-Control: no-store來控制。尤其是臨時活動頁如果CDN把302結(jié)果緩存一小時活動結(jié)束后的時間窗口里用戶還是會跳到活動頁等于白加班。4.4 問題重定向后請求方法丟失這里要回到之前提過的307/308。一個典型場景是客戶端向服務(wù)端提交一個POST請求服務(wù)端返回302并指向另一個地址瀏覽器按規(guī)范可能把這個POST請求變成GET請求導(dǎo)致新地址收到的請求沒有請求體業(yè)務(wù)報錯。最直接的解決辦法是如果后端需要保持原請求方法就用307或308如果希望把POST轉(zhuǎn)成GET例如表單提交成功后跳轉(zhuǎn)結(jié)果頁就用303或302并告知客戶端必須用GET。這里沒有一刀切的答案完全取決于業(yè)務(wù)是否需要保留請求方法。排查這類問題時建議在服務(wù)端打印請求方法、請求頭和請求體對比跳轉(zhuǎn)前后的差異。很多框架默認會把302包裝成GET請求這是框架行為不是服務(wù)器底層錯誤。5. 我在實際項目中掌握的幾條避坑經(jīng)驗5.1 優(yōu)先用簡潔規(guī)則替代復(fù)雜正則Nginx里return指令比rewrite更容易理解也更省性能。rewrite適合需要從URL中提取一段并重新拼接的場景但正則寫復(fù)雜后非常難維護。我曾經(jīng)維護過一個老站點一條rewrite規(guī)則寫了半年沒人敢碰后來直接改用兩三條return規(guī)則把所有舊路徑顯式列出來反而便于排查。我的建議是能用精確路徑就不寫正則能寫正則就別嵌套多層判斷盡量讓每一條跳轉(zhuǎn)規(guī)則一眼就能看懂。5.2 重定向目標必須做可達性自檢跳轉(zhuǎn)是否成功不只取決于狀態(tài)碼還取決于Location指向的地址是否真的能返回200。如果配了301到新地址但新地址本身是404那用戶只會看到一記重定向失效搜索引擎也把新頁面當成死鏈。我在驗收時會寫一個簡單腳本把所有要配置的舊URL和新URL成對測試確保舊URL返回3XX、新URL返回2XX并且在跳轉(zhuǎn)鏈上不出現(xiàn)404。5.3 重定向鏈越短越好從舊URL到最終頁面中間經(jīng)歷多次3XX跳轉(zhuǎn)會拖慢頁面加載速度也會讓搜索引擎的抓取預(yù)算浪費在非內(nèi)容頁上。理想狀態(tài)是舊URL只跳轉(zhuǎn)一次直達最終URL。如果有歷史原因造成中間層比如舊域名跳中間域名再跳新域名一定要盡快收斂成一次跳轉(zhuǎn)。我曾經(jīng)處理過一個項目跳轉(zhuǎn)鏈是6跳最后用curl -L看到響應(yīng)耗時翻了四倍優(yōu)化成單跳后用戶體感快了很多。5.4 留好日志和監(jiān)控重定向不是配完就結(jié)束的事。建議在Nginx或應(yīng)用層為3XX響應(yīng)單獨打日志記錄來源URL、目標URL、跳轉(zhuǎn)類型。這樣當運營反饋某個入口流量異常下降時你可以快速拉出日志看是哪個環(huán)節(jié)把流量截斷了。我習(xí)慣每季度做一次重定向?qū)徲嫲阉?XX響應(yīng)按狀態(tài)碼、跳轉(zhuǎn)目標、命中次數(shù)排序一次性清理掉那些已經(jīng)沒人訪問但還在消耗爬蟲預(yù)算的舊規(guī)則。最后再分享一個個人體會處理301和302最重要的不是記住代碼怎么寫而是建立一張這個變更到底是不是永久的判斷表。每次收到跳轉(zhuǎn)需求先問業(yè)務(wù)方三句話舊地址以后還需要正常訪問嗎這個跳轉(zhuǎn)預(yù)計持續(xù)多久目標地址是同一個站內(nèi)的內(nèi)容還是另一個系統(tǒng)的頁面答案如果是舊地址以后不用了且持續(xù)超過一個月基本都是301跑不掉如果只是暫時引導(dǎo)且需要頻繁變化302是更合適的選擇。這套判斷邏輯一旦養(yǎng)成你寫出的跳轉(zhuǎn)規(guī)則會比很多人穩(wěn)得多。