在 Ruby on Rails 應用開發中,頁面緩存是提升性能的終極武器。當 Rails 生成靜態 HTML 文件並交由 Web 服務器直接服務時,應用服務器完全繞過 Rails 堆疊,性能提升可達數量級。然而,現實世界的應用很少是完全靜態的——用戶登錄狀態、個性化內容、動態數據都會讓頁面緩存失效。
以 ChangingThePresent.org 為例,幾乎每個頁面都包含用戶相關的動態元素:登錄用戶看到「個人資料」和「登出」鏈接,未登錄用戶則看到「登錄」和「註冊」。傳統頁面緩存無法處理這種「同一 URL、不同用戶、不同內容」的場景。


作者提出三種應對方案:
第三種方案最激進也最有效:Rails 完全不參與請求處理,實現真正的靜態文件服務。
核心思想:在靜態頁面中同時渲染所有用戶類型的內容版本,通過 CSS 隱藏不需要的部分,JavaScript 讀取 Cookie 後顯示正確版本。
<div id='logged_out'> <%= link_to "login", :controller => 'members', :action => 'login' %><br /> <%= link_to "register", :controller => 'members', :action => 'signup' %> </div> <div id='logged_in' style="display: none;"> <%= link_to "your profile", :controller => 'profiles', :action => 'show' %> <%= link_to "logout", :controller => "members", :action => "logout" %> </div>
關鍵技巧:`your profile` 鏈接不帶用戶 ID,指向通用的 `profiles#index`,由控制器根據當前用戶重定向到正確資料頁。
def login
if request.post?
self.current_user = User.authenticate(params['user_login'], params['user_password'])
if logged_in?
cookies[:login] = current_user.login
cookies[:image] = find_thumb(current_user.member_image)
end
end
end
def logout
cookies.delete :login
cookies.delete :image
# ...
end
function readCookie(name) {
var nameEQ = name + "=";
var ca = document.cookie.split(';');
for(var i=0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
function handle_cached_user() {
var login_cookie = readCookie('login');
var logged_in = document.getElementById('logged_in');
var logged_out = document.getElementById('logged_out');
if(login_cookie == null) {
logged_in.style.display = 'none';
logged_out.style.display = 'block';
} else {
logged_out.style.display = 'none';
logged_in.style.display = 'block';
}
}
<script type="text/javascript">
window.onload = function() {
handle_cached_user();
// 其他 onload 代碼...
}
</script>
caches_page :index
當需要顯示具體用戶名、頭像等個性化數據時,結合 Hide-and-Seek 與 Show-and-Tell:先用 Hide-and-Seek 選擇正確的 partial,再用 JavaScript 將 Cookie 中的數據注入 DOM。

<div id='logged_in' style="display: none;">
<%= link_to "Hi, <b id='bold_link'>my_login</b>", ... %>
<div id='picture_and_link'>
<a id='link_for_member_thumbnail' href="...">
<img id='member_thumbnail' src="/images/default/def_member_thumbnail.gif" />
</a>
</div>
<div id="not_mine">Not my_login?</div>
<%= image_button "logout", ... %>
</div>
function handle_user_partial() {
var login_cookie = readCookie('login');
var image_cookie = readCookie('image');
document.getElementById('profile_link').href = '/member/' + login_cookie;
document.getElementById('bold_link').firstChild.nodeValue = login_cookie;
document.getElementById('not_mine').firstChild.nodeValue = "Not " + login_cookie + "?";
document.getElementById('link_for_member_thumbnail').href = "/member/" + login_cookie;
document.getElementById('member_thumbnail').src = image_cookie.replace(/%2[Ff]/g, "/");
document.getElementById('member_thumbnail').alt = login_cookie;
}
function handle_cached_user() {
var login_cookie = readCookie('login');
var logged_in = document.getElementById('logged_in');
var logged_out = document.getElementById('logged_out');
if(login_cookie == null) {
logged_in.style.display = 'none';
logged_out.style.display = 'block';
} else {
handle_user_partial(); // 先注入數據
logged_out.style.display = 'none';
logged_in.style.display = 'block';
}
}
頁面緩存不會自動過期,需要定期清理。最簡單方式:定時任務刪除 `public/index.html`。對於更複雜的場景,可使用 Sweeper 觀察模型變更精確失效。
ChangingThePresent.org 通過這套方案:
代價是增加了 HTML/JavaScript 維護複雜度,但換來的是極致的可擴展性。下一篇將探討 ActiveRecord 性能優化技巧。