diff --git a/.gitignore b/.gitignore index 0a51671..077a660 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,5 @@ pnpm-debug.log* .vscode/ .cursor/ .vite-hooks/ +.wrangler/ .learnings/ diff --git a/apps/root/public/.well-known/security.txt b/apps/root/public/.well-known/security.txt new file mode 100644 index 0000000..a93537f --- /dev/null +++ b/apps/root/public/.well-known/security.txt @@ -0,0 +1,4 @@ +Contact: mailto:zrr1999@qq.com +Expires: 2027-06-18T00:00:00Z +Preferred-Languages: zh, en +Canonical: https://zrr.dev/.well-known/security.txt diff --git a/apps/root/public/sitemap.xml b/apps/root/public/sitemap.xml new file mode 100644 index 0000000..b1a4ed2 --- /dev/null +++ b/apps/root/public/sitemap.xml @@ -0,0 +1,8 @@ + + + + https://zrr.dev/ + monthly + 1.0 + + diff --git a/apps/root/src/components/Layout.astro b/apps/root/src/components/Layout.astro index fe72489..89d99dd 100644 --- a/apps/root/src/components/Layout.astro +++ b/apps/root/src/components/Layout.astro @@ -10,16 +10,31 @@ const { title = "六个骨头的个人主页", description = "六个骨头的个人网站主页,包含博客、幻灯片等内容", } = Astro.props; +const canonicalUrl = new URL(Astro.url.pathname, Astro.site ?? "https://zrr.dev"); --- - + + + + + + + + + + + + + + {title} + diff --git a/apps/root/src/worker.ts b/apps/root/src/worker.ts new file mode 100644 index 0000000..06546cf --- /dev/null +++ b/apps/root/src/worker.ts @@ -0,0 +1,88 @@ +/// + +interface AssetFetcher { + fetch(input: RequestInfo | URL, init?: RequestInit): Promise; +} + +interface Env { + ASSETS: AssetFetcher; +} + +const CONTENT_SECURITY_POLICY = [ + "default-src 'self'", + "base-uri 'self'", + "object-src 'none'", + "frame-ancestors 'none'", + "form-action 'self'", + "img-src 'self' data: https:", + "font-src 'self' https://fonts.gstatic.com", + "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com", + "script-src 'self' 'unsafe-inline' https://challenges.cloudflare.com", + "connect-src 'self' https://challenges.cloudflare.com", + "frame-src 'self' https://challenges.cloudflare.com", + "upgrade-insecure-requests", +].join("; "); + +const SECURITY_HEADERS = { + "Content-Security-Policy": CONTENT_SECURITY_POLICY, + "Cross-Origin-Opener-Policy": "same-origin", + "Permissions-Policy": [ + "accelerometer=()", + "camera=()", + "geolocation=()", + "gyroscope=()", + "magnetometer=()", + "microphone=()", + "payment=()", + "usb=()", + ].join(", "), + "Referrer-Policy": "strict-origin-when-cross-origin", + "Strict-Transport-Security": "max-age=31536000", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "DENY", + "X-XSS-Protection": "0", +} as const; + +const IMMUTABLE_ASSET_PATH = + /^\/_astro\/.+\.(?:css|js|mjs|png|jpe?g|webp|avif|gif|svg|ico|woff2?)$/i; +const STATIC_ASSET_PATH = + /\.(?:css|js|mjs|png|jpe?g|webp|avif|gif|svg|ico|woff2?|txt|xml)$/i; + +function appendHeaders(request: Request, response: Response): Response { + const securedResponse = new Response(response.body, response); + + for (const [header, value] of Object.entries(SECURITY_HEADERS)) { + securedResponse.headers.set(header, value); + } + + const { pathname } = new URL(request.url); + if (response.ok && IMMUTABLE_ASSET_PATH.test(pathname)) { + securedResponse.headers.set( + "Cache-Control", + "public, max-age=31536000, immutable" + ); + } else if (response.ok && STATIC_ASSET_PATH.test(pathname)) { + securedResponse.headers.set("Cache-Control", "public, max-age=86400"); + } + + return securedResponse; +} + +function redirectToApex(request: Request): Response | undefined { + const url = new URL(request.url); + if (url.hostname !== "www.zrr.dev") return undefined; + + url.hostname = "zrr.dev"; + const response = Response.redirect(url, 308); + return appendHeaders(request, response); +} + +export default { + async fetch(request: Request, env: Env): Promise { + const redirect = redirectToApex(request); + if (redirect) return redirect; + + const response = await env.ASSETS.fetch(request); + return appendHeaders(request, response); + }, +}; diff --git a/apps/root/tsconfig.json b/apps/root/tsconfig.json index c5450d3..30aaa27 100644 --- a/apps/root/tsconfig.json +++ b/apps/root/tsconfig.json @@ -2,6 +2,7 @@ "extends": "astro/tsconfigs/strict", "compilerOptions": { "baseUrl": ".", + "lib": ["ES2022", "DOM", "DOM.Iterable"], "paths": { "@/*": ["src/*"] } diff --git a/apps/root/wrangler.jsonc b/apps/root/wrangler.jsonc index 7ca3138..2cfa7bf 100644 --- a/apps/root/wrangler.jsonc +++ b/apps/root/wrangler.jsonc @@ -2,8 +2,11 @@ "$schema": "../../node_modules/wrangler/config-schema.json", "name": "zrr-website-root", "compatibility_date": "2026-05-08", + "main": "./src/worker.ts", "assets": { "directory": "./dist", + "binding": "ASSETS", + "run_worker_first": true, }, "routes": [ {