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": [
{