App\Support\CustomCacheProfile::class // 使用自訂 profile | - 'hasher' => App\Support\CustomCacheHasher::class // 使用自訂 hasher | - 'replacers' => [] // 移除 CsrfTokenReplacer | - 'add_cache_time_header' => true // 顯示快取時間(除錯用) | - 'add_cache_age_header' => true // 顯示快取年齡(除錯用) | | === 驗證快取是否生效 === | | 1. 清除快取: | php artisan responsecache:clear | | 2. 第一次請求(建立快取): | curl -s https://your-domain.com/ > /dev/null | | 3. 第二次請求(應該使用快取): | curl -I https://your-domain.com/ | | 4. 確認以下 headers 出現: | - laravel-responsecache: [timestamp] // 表示這是快取的回應 | - laravel-responsecache-age: [seconds] // 快取已存在多久 | - Cache-Control: public, max-age=604800 // 正確的快取控制 | | 5. 檢查快取檔案: | ls -la storage/framework/cache/data/ | | === 重要注意事項 === | | 1. 安全性考量: | - 這個快取機制會讓所有使用者共用相同的快取內容 | - **絕對不要**在包含使用者特定資料的頁面上使用此機制 | - **絕對不要**在需要認證的頁面上使用此機制 | - 範例:dashboard、個人資料頁面、購物車等都不應該快取 | | 2. 快取失效: | - 當內容更新時,需要手動清除快取:php artisan responsecache:clear | - 考慮在 CMS 內容更新時自動觸發快取清除 | - 或使用 cache tags(需要 Redis)來精確清除特定頁面的快取 | | 3. X-Inertia Header: | - 初次載入:瀏覽器請求完整 HTML(沒有 X-Inertia header) | - Inertia 導航:發送 AJAX 請求,只回傳 JSON(有 X-Inertia header) | - CustomCacheHasher 會根據這個 header 產生不同的快取 key | - 確保 HTML 和 JSON 回應分別快取 | | 4. HEAD vs GET 請求: | - CustomCacheProfile 必須支援 HEAD 請求 | - curl -I 使用 HEAD 請求,如果不支援會導致快取驗證失敗 | - HEAD 和 GET 共用同一份快取(因為 CustomCacheHasher 不區分它們) | | 5. Cookie 處理: | - 即使使用快取,Laravel 仍會為每個請求產生新的 XSRF-TOKEN 和 session | - 這是正常的,不會影響快取機制 | - 主要的 HTML/JSON 內容來自快取,只有 cookies 是新產生的 | | 6. 效能影響: | - 第一次請求:正常執行時間(需要跑 PHP + 資料庫) | - 後續請求:大幅減少(直接回傳快取的 HTML/JSON,不經過 Laravel) | - 預期可減少 80-90% 的伺服器負載 | | === 疑難排解 === | | 如果快取沒有生效,檢查以下項目: | | 1. 確認 storage/framework/cache/data/ 目錄有寫入權限 | 2. 確認 .env 中 RESPONSE_CACHE_ENABLED=true | 3. 執行 php artisan config:clear 清除設定快取 | 4. 檢查 storage/logs/laravel.log 是否有錯誤訊息 | 5. 確認路由有套用 CacheResponse middleware | 6. 使用完整 URL(包含 https://)進行測試 | 7. 避免使用瀏覽器測試(瀏覽器可能有自己的快取),改用 curl | | === 修改的檔案清單(完整記錄)=== | | 1. routes/web.php (本檔案) | - 新增詳細的快取實作說明註解 | - 在部分路由上套用 CacheResponse middleware | | 2. app/Support/CustomCacheProfile.php (新建檔案) | - 實作 Spatie\ResponseCache\CacheProfiles\CacheProfile 介面 | - 定義快取策略(哪些請求應該快取、快取多久等) | | 3. app/Support/CustomCacheHasher.php (新建檔案) | - 實作 Spatie\ResponseCache\Hasher\RequestHasher 介面 | - 自訂快取 key 生成邏輯(忽略 cookies) | | 4. app/Http/Middleware/RemoveInertiaNoCache.php (新建檔案) | - 新增 middleware 來移除 Inertia 的 no-cache header | - 將 Cache-Control 改為 public, max-age=604800 | | 5. bootstrap/app.php (修改) | - 新增 use App\Http\Middleware\RemoveInertiaNoCache; | - 在 $middleware->web(append: [...]) 中加入 RemoveInertiaNoCache::class | | 6. config/responsecache.php (修改) | - 'cache_profile' 改為 App\Support\CustomCacheProfile::class | - 'hasher' 改為 App\Support\CustomCacheHasher::class | - 'add_cache_time_header' 改為 true | - 'add_cache_age_header' 改為 true | - 'replacers' 清空陣列(註解掉 CsrfTokenReplacer) | | 7. storage/responsecache/ (新建目錄) | - 建立快取目錄並設定權限(775, www-data:www-data) | - 實際快取檔案儲存在 storage/framework/cache/data/ 目錄 | | === 如何移除快取功能 === | | 如果未來需要完全移除這個快取機制,請按照以下步驟操作: | | 步驟 1:移除路由 middleware | ---------------------- | 編輯 routes/web.php,找到並移除: | | Route::middleware([CacheResponse::class])->group(function () { | // ... 路由定義 ... | }); | | 改為直接定義路由,不套用 CacheResponse middleware: | | Route::get('/', [HomeController::class, 'index'])->name('home'); | Route::get('/about', function () { return Inertia::render('about'); })->name('about'); | // ... 其他路由 ... | | 同時移除 use Spatie\ResponseCache\Middlewares\CacheResponse; | | 步驟 2:從 bootstrap/app.php 移除 RemoveInertiaNoCache middleware | ------------------------------------------------------------------ | 編輯 bootstrap/app.php: | | 1. 移除這一行: | use App\Http\Middleware\RemoveInertiaNoCache; | | 2. 在 $middleware->web(append: [...]) 中移除: | RemoveInertiaNoCache::class, | | 結果應該是: | $middleware->web(append: [ | HandleAppearance::class, | HandleInertiaRequests::class, | AddLinkHeadersForPreloadedAssets::class, | // RemoveInertiaNoCache::class, <- 移除這行 | ]); | | 步驟 3:清除快取並重新載入設定 | -------------------------------- | 執行以下指令: | | php artisan responsecache:clear # 清除所有 response cache | php artisan cache:clear # 清除應用快取 | php artisan config:clear # 清除設定快取 | | 步驟 4:(可選)刪除自訂類別檔案 | -------------------------------- | 如果確定不再需要這些檔案,可以刪除: | | rm app/Support/CustomCacheProfile.php | rm app/Support/CustomCacheHasher.php | rm app/Http/Middleware/RemoveInertiaNoCache.php | | 步驟 5:(可選)還原 config/responsecache.php | -------------------------------------------- | 編輯 config/responsecache.php,還原為預設值: | | 'cache_profile' => Spatie\ResponseCache\CacheProfiles\CacheAllSuccessfulGetRequests::class, | 'hasher' => Spatie\ResponseCache\Hasher\DefaultHasher::class, | 'add_cache_time_header' => env('APP_DEBUG', false), | 'add_cache_age_header' => env('RESPONSE_CACHE_AGE_HEADER', false), | 'replacers' => [ | \Spatie\ResponseCache\Replacers\CsrfTokenReplacer::class, | ], | | 步驟 6:(可選)停用 Response Cache | ----------------------------------- | 在 .env 檔案中加入或修改: | | RESPONSE_CACHE_ENABLED=false | | 或者直接解除安裝套件: | | composer remove spatie/laravel-responsecache | | 步驟 7:驗證快取已完全移除 | -------------------------- | 重新整理頁面,確認以下 headers 消失: | | - laravel-responsecache | - laravel-responsecache-age | | 並且 Cache-Control 恢復為: | | Cache-Control: no-cache, private | | === 注意事項 === | | - 移除快取功能後,伺服器負載會增加,頁面載入時間會變慢 | - 如果只是要暫時停用,建議使用 .env 設定而非刪除檔案 | - 刪除檔案前請確保有備份或版本控制 | - 移除後記得清除所有快取檔案,避免殘留過期資料 | | 最後更新:2025-11-23 | 實作者:GitHub Copilot (AI Assistant) | */ use Illuminate\Support\Facades\Route; use Inertia\Inertia; use App\Http\Controllers\HomeController; use App\Http\Controllers\ContactUsController; // Controllers use App\Http\Controllers\WpApiController; use App\Http\Controllers\PeopleController; use App\Http\Controllers\ExecutiveController; use Spatie\ResponseCache\Middlewares\CacheResponse; Route::middleware([CacheResponse::class])->group(function () { Route::get('/', [HomeController::class, 'index'])->name('home'); Route::get('/about', function () { return Inertia::render('about'); })->name('about'); Route::get('/impact', function () { return Inertia::render('impact'); })->name('impact'); Route::get('/news', function () { return Inertia::render('news'); })->name('news'); Route::get('/people', [PeopleController::class, 'index'])->name('people'); Route::get('/executive', [ExecutiveController::class, 'index'])->name('executive'); }); Route::get('/contact-us', [ContactUsController::class, 'create'])->name('contact'); Route::get('/test', function () { return Inertia::render('Test', [ 'name' => 'Test', 'description' => 'This is a test page.', 'items' => [ ['id' => 1, 'name' => 'Item 1'], ['id' => 2, 'name' => 'Item 2'], ['id' => 3, 'name' => 'Item 3'], ], ]); })->name('test'); Route::get('api/news', [WpApiController::class, 'getNews'])->name('api.news'); Route::get('/news-detail', function () { return Inertia::render('news-detail'); })->name('news-detail'); Route::get('/event-detail', function () { return Inertia::render('event-detail'); })->name('event-detail'); Route::get('/partner-with-us', function () { return Inertia::render('partner-with-us'); })->name('partner'); Route::get('/ai-in-taiwan', function () { return Inertia::render('ai-in-taiwan'); })->name('ai-in-taiwan'); Route::get('/voice-of-impact', function () { return Inertia::render('voice-of-impact'); })->name('voice-of-impact'); Route::get('/certification', function () { return Inertia::render('certification'); })->name('certification'); Route::get('/report', function () { return Inertia::render('report'); })->name('report'); Route::get('/test-wp-post/{slug}', [App\Http\Controllers\TestWpPostController::class, 'show'])->name('test-wp-post.show'); Route::middleware(['auth', 'verified'])->group(function () { Route::get('dashboard', function () { return Inertia::render('dashboard'); })->name('dashboard'); }); require __DIR__ . '/settings.php'; require __DIR__ . '/auth.php'; Route::get('test', function () { return "
Hello, this is a test route!
"; })->name('test.route');