-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnginx.conf
More file actions
527 lines (453 loc) · 21 KB
/
nginx.conf
File metadata and controls
527 lines (453 loc) · 21 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
worker_processes auto;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
# Performance
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
# Gzip compression (優化配置)
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_comp_level 6; # 平衡壓縮率與 CPU
gzip_proxied any;
gzip_disable "msie6";
gzip_types
text/plain
text/css
text/xml
text/javascript
application/x-javascript
application/xml+rss
application/json
application/javascript
application/ld+json
application/manifest+json
image/svg+xml;
server {
listen 8080;
server_name _;
root /usr/share/nginx/html;
index index.html;
# HTTP/2 優化
http2_push_preload on;
# [fix:2025-11-06] 防止內部端口洩漏到重定向
# 參考: http://nginx.org/en/docs/http/ngx_http_core_module.html#absolute_redirect
absolute_redirect off;
port_in_redirect off;
# [fix:2025-12-31] 正確處理反向代理的 HTTPS
# Cloudflare/Zeabur 使用 SSL 終止,需要檢測 X-Forwarded-Proto
# 用於 301 重定向時使用正確的 scheme
# 參考: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto
set $redirect_scheme $scheme;
if ($http_x_forwarded_proto = "https") {
set $redirect_scheme "https";
}
# ============================================================
# [2026-01-07] 安全標頭已遷移至 Cloudflare
# ============================================================
# 所有 CSP、HSTS、X-Frame-Options 等安全標頭現在由 Cloudflare Worker 處理
# 請參考: docs/CLOUDFLARE_SECURITY_HEADERS_GUIDE.md
#
# 遷移原因:
# 1. 集中管理 - 一處設定,所有子網域自動生效
# 2. 即時生效 - 不需要重新部署應用
# 3. 高可用性 - 即使 Origin 故障,安全標頭仍然生效
# 4. 避免衝突 - Cloudflare 和 Nginx 同時設定會導致標頭重複
#
# Nginx 僅保留:
# - 快取控制 (Cache-Control)
# - Gzip 壓縮
# - SPA 路由
# ============================================================
# [critical:2025-11-08] Service Worker 檔案 - 必須放在通用 JS 規則之前
# PWA 最佳實踐:sw.js 永遠不快取,確保用戶立即獲得更新
# 參考: https://web.dev/articles/service-worker-lifecycle
# 子路徑 PWA Service Worker 腳本(含 park-keeper, split-meow)
location ~* ^/(?:ratewise|nihonname|quake-school|park-keeper|split-meow)/(?:assets/)?(sw|workbox-[^/]*|registerSW)\.js$ {
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
# 根據路徑決定檔案位置
# ratewise -> /usr/share/nginx/html (通過 symlink)
# nihonname -> /usr/share/nginx/html/nihonname-app (通過 symlink)
try_files $uri =404;
}
# 靜態資源快取策略(活躍開發階段:短期快取)
# 理由:專案處於活躍開發階段,頻繁部署,1年快取會導致:
# 1. CDN 邊緣節點快取不一致
# 2. 使用者瀏覽器快取舊版本資產
# 3. 需要手動清除 Cloudflare 快取
# 穩定生產階段後可改為 1y + immutable
# 參考:https://web.dev/articles/http-cache
# 通用靜態資源(排除 SW / Workbox bundle)
location ~* ^/(?!.*(?:sw|workbox-[^/]*|registerSW)\.js).*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot|avif|webp|br|gz)$ {
expires 1d;
add_header Cache-Control "public, max-age=86400";
access_log off;
}
# HTML 不快取(確保更新即時生效)
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# Service Worker 不快取
location = /sw.js {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# SEO 靜態檔案優先處理 - 必須在 SPA routing 之前
# 確保 sitemap.xml 和 robots.txt 正確返回而不是被重定向到 index.html
location = /sitemap.xml {
add_header Content-Type application/xml;
add_header Cache-Control "public, max-age=3600";
try_files $uri =404;
}
location = /robots.txt {
add_header Content-Type text/plain;
add_header Cache-Control "public, max-age=3600";
try_files $uri =404;
}
location = /manifest.webmanifest {
add_header Content-Type application/manifest+json;
add_header Cache-Control "public, max-age=86400"; # 1 day cache
try_files $uri =404;
}
location = /llms.txt {
add_header Content-Type text/plain;
add_header Cache-Control "public, max-age=3600";
try_files $uri =404;
}
# [fix:2025-12-14] 確保 SEO 文件正確提供(修正指向 ratewise-app 目錄)
location = /ratewise/sitemap.xml {
add_header Content-Type application/xml;
add_header Cache-Control "public, max-age=3600";
alias /usr/share/nginx/html/ratewise-app/sitemap.xml;
}
# [fix:2025-12-14] 修正指向 ratewise-app 目錄
location = /ratewise/manifest.webmanifest {
add_header Content-Type application/manifest+json;
add_header Cache-Control "no-cache, must-revalidate";
alias /usr/share/nginx/html/ratewise-app/manifest.webmanifest;
}
location = /ratewise/llms.txt {
add_header Content-Type text/plain;
add_header Cache-Control "public, max-age=3600";
# [fix:2025-12-14] 修正指向 ratewise-app 目錄
alias /usr/share/nginx/html/ratewise-app/llms.txt;
}
# [fix:2025-12-14] 修正指向 ratewise-app 目錄
location = /ratewise/robots.txt {
add_header Content-Type text/plain;
add_header Cache-Control "public, max-age=3600";
alias /usr/share/nginx/html/ratewise-app/robots.txt;
}
# ============================================================
# [fix:2025-12-03] NihonName 專案路由配置
# 確保 /nihonname/ 路徑獨立於 ratewise,不會被 SPA fallback 影響
# ============================================================
# NihonName SEO 靜態檔案
location = /nihonname/sitemap.xml {
add_header Content-Type application/xml;
add_header Cache-Control "public, max-age=3600";
alias /usr/share/nginx/html/nihonname-app/sitemap.xml;
}
location = /nihonname/robots.txt {
add_header Content-Type text/plain;
add_header Cache-Control "public, max-age=3600";
alias /usr/share/nginx/html/nihonname-app/robots.txt;
}
location = /nihonname/manifest.webmanifest {
add_header Content-Type application/manifest+json;
add_header Cache-Control "no-cache, must-revalidate";
alias /usr/share/nginx/html/nihonname-app/manifest.webmanifest;
}
location = /nihonname/llms.txt {
add_header Content-Type text/plain;
add_header Cache-Control "public, max-age=3600";
alias /usr/share/nginx/html/nihonname-app/llms.txt;
}
# NihonName 尾斜線統一 (301 重定向)
# [fix:2025-12-31] 使用 $redirect_scheme 確保 Cloudflare 代理下返回 https://
location = /nihonname {
return 301 $redirect_scheme://$host/nihonname/;
}
# ============================================================
# [fix:2025-12-29] Quake-School 專案路由配置
# 地震防災教育平台,支援多頁面 SSG
# ============================================================
# Quake-School SEO 靜態檔案
location = /quake-school/sitemap.xml {
add_header Content-Type application/xml;
add_header Cache-Control "public, max-age=3600";
alias /usr/share/nginx/html/quake-school-app/sitemap.xml;
}
location = /quake-school/robots.txt {
add_header Content-Type text/plain;
add_header Cache-Control "public, max-age=3600";
alias /usr/share/nginx/html/quake-school-app/robots.txt;
}
location = /quake-school/manifest.webmanifest {
add_header Content-Type application/manifest+json;
add_header Cache-Control "no-cache, must-revalidate";
alias /usr/share/nginx/html/quake-school-app/manifest.webmanifest;
}
location = /quake-school/llms.txt {
add_header Content-Type text/plain;
add_header Cache-Control "public, max-age=3600";
alias /usr/share/nginx/html/quake-school-app/llms.txt;
}
# Park-Keeper 路由與靜態檔案
# Park-Keeper SEO 靜態檔案
location = /park-keeper/sitemap.xml {
add_header Content-Type application/xml;
add_header Cache-Control "public, max-age=3600";
alias /usr/share/nginx/html/park-keeper-app/sitemap.xml;
}
location = /park-keeper/robots.txt {
add_header Content-Type text/plain;
add_header Cache-Control "public, max-age=3600";
alias /usr/share/nginx/html/park-keeper-app/robots.txt;
}
location = /park-keeper/manifest.webmanifest {
add_header Content-Type application/manifest+json;
add_header Cache-Control "no-cache, must-revalidate";
alias /usr/share/nginx/html/park-keeper-app/manifest.webmanifest;
}
location = /park-keeper/llms.txt {
add_header Content-Type text/plain;
add_header Cache-Control "public, max-age=3600";
alias /usr/share/nginx/html/park-keeper-app/llms.txt;
}
# Park-Keeper 尾斜線統一 (301 重定向)
location = /park-keeper {
return 301 $redirect_scheme://$host/park-keeper/;
}
# Quake-School 尾斜線統一 (301 重定向)
# [fix:2025-12-31] 使用 $redirect_scheme 確保 Cloudflare 代理下返回 https://
location = /quake-school {
return 301 $redirect_scheme://$host/quake-school/;
}
# Quake-School SPA 路由
location /quake-school/ {
alias /usr/share/nginx/html/quake-school-app/;
index index.html;
try_files $uri $uri/ @quake_school_html_fallback;
}
# Quake-School HTML fallback
location @quake_school_html_fallback {
rewrite ^/quake-school/(.+)/$ /quake-school/$1.html last;
rewrite ^/quake-school/(.+)$ /quake-school/$1.html last;
}
# 處理 rewrite 後的 .html 檔案請求
location ~ ^/quake-school/(.+)\.html$ {
alias /usr/share/nginx/html/quake-school-app/$1.html;
default_type text/html;
}
# Park-Keeper SSG 路由
location /park-keeper/ {
alias /usr/share/nginx/html/park-keeper-app/;
index index.html;
try_files $uri $uri/ @park_keeper_html_fallback;
}
# Park-Keeper HTML fallback
location @park_keeper_html_fallback {
rewrite ^/park-keeper/(.+)/$ /park-keeper/$1.html last;
rewrite ^/park-keeper/(.+)$ /park-keeper/$1.html last;
}
# 處理 rewrite 後的 .html 檔案請求
location ~ ^/park-keeper/(.+)\.html$ {
alias /usr/share/nginx/html/park-keeper-app/$1.html;
default_type text/html;
}
# NihonName SPA 路由 - 必須在通用 SPA fallback 之前
# [fix:2025-12-05] 根據 context7 Nginx 官方文檔最佳實踐
# 參考: [context7:/websites/nginx_en:2025-12-05] alias + try_files + error_page
#
# 問題:vite-react-ssg 產生 /history.html 而非 /history/index.html
# 解法:使用 try_files 嘗試 $uri.html(如 /history/ → /history.html)
location /nihonname/ {
alias /usr/share/nginx/html/nihonname-app/;
index index.html;
# 嘗試順序:
# 1. 直接檔案 ($uri)
# 2. 目錄下的 index.html ($uri/)
# 3. 去掉尾斜線加 .html(/history/ → /history.html)
# 4. SPA fallback
try_files $uri $uri/ @nihonname_html_fallback;
}
# NihonName HTML fallback - 處理 /path/ → /path.html
# [fix:2025-12-06] 修正 alias 與 try_files 搭配問題
# 問題:named location 無法使用 alias,改用 internal rewrite
location @nihonname_html_fallback {
# 使用 internal rewrite 到正確的 .html 檔案
# /nihonname/history/ → /nihonname/history.html
# /nihonname/about → /nihonname/about.html
rewrite ^/nihonname/(.+)/$ /nihonname/$1.html last;
rewrite ^/nihonname/(.+)$ /nihonname/$1.html last;
}
# 處理 rewrite 後的 .html 檔案請求
location ~ ^/nihonname/(.+)\.html$ {
alias /usr/share/nginx/html/nihonname-app/$1.html;
default_type text/html;
}
# [fix:2025-11-07] 優化靜態資源快取策略
# 參考: https://web.dev/articles/http-cache
# [critical:2025-11-08] index.html 永遠不快取 - PWA 更新的關鍵
# 包含 ratewise, nihonname, quake-school, park-keeper, split-meow 路徑
# 參考: https://web.dev/articles/http-cache
location ~* ^/(?:ratewise|nihonname|quake-school|park-keeper|split-meow)/index\.html$ {
add_header Cache-Control "no-cache, must-revalidate" always;
try_files $uri =404;
}
# 圖片資源 (AVIF/WebP/PNG)
# [SEO Fix 2025-11-24] 允許跨域訪問,確保 og-image 可被 Google/社交媒體抓取
# 參考: fix/seo-indexing-phase1
location ~* \.(avif|webp|png|jpg|jpeg|gif|svg|ico)$ {
expires 1d;
add_header Cache-Control "public, max-age=86400";
add_header Vary "Accept"; # 支援內容協商
add_header Access-Control-Allow-Origin "*"; # CORS 支援
add_header Cross-Origin-Resource-Policy "cross-origin"; # 允許跨域嵌入
}
# 字型檔案
# [SEO Fix 2025-11-24] 允許跨域訪問,確保 Google Fonts 正常載入
# 參考: fix/seo-indexing-phase1
location ~* \.(woff|woff2|ttf|eot|otf)$ {
expires 1d;
add_header Cache-Control "public, max-age=86400";
add_header Access-Control-Allow-Origin "*"; # CORS 支援
add_header Cross-Origin-Resource-Policy "cross-origin"; # 允許跨域嵌入
}
# [SEO:2025-12-01] 301 統一尾斜線 - 使用絕對 URL(RFC 7231 標準)
# [fix:2025-12-31] 使用 $redirect_scheme 確保 Cloudflare 代理下返回 https://
# 避免 /ratewise 與 /ratewise/ 權重分散;排除含擴展名的靜態檔案
# 參考: https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.2
# 參考: https://moz.com/learn/seo/duplicate-content
if ($request_uri ~ ^(/ratewise[^\.\?]*[^/])$) {
return 301 $redirect_scheme://$host$1/;
}
# [SEO:2025-12-01] /ratewise 重定向到 /ratewise/ - 使用絕對 URL
# [fix:2025-12-31] 使用 $redirect_scheme 確保 Cloudflare 代理下返回 https://
# 入口 URL 強制 301 → 尾斜線,避免權重分散
# 參考: https://web.dev/add-manifest/#start_url
location = /ratewise {
return 301 $redirect_scheme://$host/ratewise/;
}
# [fix:2026-03-06] 舊社群圖片 URL 301 相容轉址
# 避免歷史分享連結指向已淘汰的 PNG 資產造成 404
location = /ratewise/og-image.png {
return 301 $redirect_scheme://$host/ratewise/og-image.jpg;
}
location = /ratewise/twitter-image.png {
return 301 $redirect_scheme://$host/ratewise/twitter-image.jpg;
}
# [fix:2026-03-07] 網路探測端點 - exact match 優先於 alias/try_files 避免 301 trailing slash redirect
# Cloudflare Worker 部署時會在邊緣攔截並回傳 204;Worker 未部署時由 nginx 兜底
location = /ratewise/__network_probe__ {
add_header Cache-Control "no-store" always;
add_header X-Probe-Source "nginx-fallback" always;
return 204;
}
# [fix:2026-03-07] CSP 違規回報端點(ratewise 路由)
# Worker 優先處理;nginx 兜底避免 POST 跟隨 301 redirect 後 ERR_FAILED
location = /ratewise/csp-report {
access_log /var/log/nginx/csp-violations.log;
add_header Cache-Control "no-store" always;
return 204;
}
# [FIX:2025-12-14] RateWise SPA 路由配置
# 使用 alias 指向正確的 ratewise-app/ 目錄,與 nihonname 配置保持一致
location /ratewise/ {
alias /usr/share/nginx/html/ratewise-app/;
index index.html;
try_files $uri $uri/ @ratewise_html_fallback;
}
# RateWise HTML fallback - 處理 /path/ → /path.html
location @ratewise_html_fallback {
rewrite ^/ratewise/(.+)/$ /ratewise/$1.html last;
rewrite ^/ratewise/(.+)$ /ratewise/$1.html last;
}
# 處理 rewrite 後的 .html 檔案請求
location ~ ^/ratewise/(.+)\.html$ {
alias /usr/share/nginx/html/ratewise-app/$1.html;
default_type text/html;
}
# ============================================================
# Split-Meow PWA 路由配置
# ============================================================
location = /split-meow/sitemap.xml {
add_header Content-Type application/xml;
add_header Cache-Control "public, max-age=3600";
alias /usr/share/nginx/html/split-meow-app/sitemap.xml;
}
location = /split-meow/robots.txt {
add_header Content-Type text/plain;
add_header Cache-Control "public, max-age=3600";
alias /usr/share/nginx/html/split-meow-app/robots.txt;
}
location = /split-meow/llms.txt {
add_header Content-Type text/plain;
add_header Cache-Control "public, max-age=3600";
alias /usr/share/nginx/html/split-meow-app/llms.txt;
}
location = /split-meow/manifest.webmanifest {
add_header Content-Type application/manifest+json;
add_header Cache-Control "no-cache, must-revalidate";
alias /usr/share/nginx/html/split-meow-app/manifest.webmanifest;
}
# Split-Meow 尾斜線統一
location = /split-meow {
return 301 $redirect_scheme://$host/split-meow/;
}
# Split-Meow SPA 路由
# [fix:2026-03-28] 使用 =404 作為 try_files 最後手段,讓未知路徑回傳真正的 404
# split-meow 是純 SPA(不含 client-side URL routing),所有功能在 /split-meow/ 根路徑
location /split-meow/ {
alias /usr/share/nginx/html/split-meow-app/;
index index.html;
try_files $uri $uri/ =404;
}
# ============================================================
# [fix:2025-12-13] haotool 首頁路由配置
# haotool 作為根路徑首頁,支援多頁面 SSG
# ============================================================
# haotool 頁面路由(處理 /projects/, /about/, /contact/)
# [fix:2025-12-31] 使用 $redirect_scheme 確保 Cloudflare 代理下返回 https://
location ~ ^/(projects|about|contact)/?$ {
# 確保尾斜線
if ($request_uri !~ /$) {
return 301 $redirect_scheme://$host$uri/;
}
try_files $uri $uri/index.html =404;
}
# haotool 根路由與未知路徑控制
location / {
try_files $uri $uri/ =404;
}
# Health check endpoint
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# CSP 違規報告端點
# [ref: OWASP CSP Best Practices 2025-10-26]
location /csp-report {
# 記錄到 error.log 以便監控
access_log /var/log/nginx/csp-violations.log;
# 返回 204 No Content (標準做法)
return 204;
}
}
}