From 7cd9833860df0c2df75710b46ac5480ca278aeed Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Fri, 15 May 2026 19:51:18 +0700 Subject: [PATCH] feat(exampleSite): showcase v0.5 opt-ins + ship deploy templates Updates exampleSite hugo.toml with v0.5 parameters for multi-section bio, QR code, vCard, share button, analytics, and OG image generation. Includes Vercel and Netlify deployment configuration templates. Enables one-click deployment for new users. --- exampleSite/hugo.toml | 57 +++++++++++++++++++++++++++++++++++++ exampleSite/static/_headers | 24 ++++++++++++++++ exampleSite/vercel.json | 33 +++++++++++++++++++++ 3 files changed, 114 insertions(+) create mode 100644 exampleSite/static/_headers create mode 100644 exampleSite/vercel.json diff --git a/exampleSite/hugo.toml b/exampleSite/hugo.toml index 07eb530..09b75f7 100644 --- a/exampleSite/hugo.toml +++ b/exampleSite/hugo.toml @@ -7,6 +7,29 @@ theme = "bonsai" # v0.4 demo (set params.rss = true and remove "RSS" from disableKinds). disableKinds = ["taxonomy", "term", "sitemap", "404"] +# Register VCard + Manifest output formats so layouts/_default/vcard.vcf and +# layouts/_default/manifest.webmanifest get rendered when their params are on. +[mediaTypes."text/vcard"] + suffixes = ["vcf"] + +[mediaTypes."application/manifest+json"] + suffixes = ["webmanifest"] + +[outputs] + home = ["HTML", "RSS", "Manifest"] + +[outputFormats.VCard] + mediaType = "text/vcard" + baseName = "vcard" + isPlainText = true + notAlternative = true + +[outputFormats.Manifest] + mediaType = "application/manifest+json" + baseName = "manifest" + isPlainText = true + notAlternative = true + [params] name = "Tien Nguyen" tagline = "Software engineer · tending small things with care" @@ -21,6 +44,40 @@ disableKinds = ["taxonomy", "term", "sitemap", "404"] footerText = "© 2026 · made with bonsai" # ogImageUrl = "/og.png" # optional 1200×630 image for social previews + # Phase 1 (v0.5) opt-ins: + themeColor = "#c93f63" # matches the sakura palette accent; tints mobile browser chrome + # robots = "index,follow" # default; set "noindex,nofollow" to hide from search engines + # preloadAvatar = true # default true; preloads params.avatar as LCP image (local paths only) + + # Phase 6 (v0.5) opt-ins: + schemaWebSite = true # emit secondary WebSite JSON-LD for richer search results + manifest = true # serve /manifest.webmanifest + emit + # themeBackground = "#fff5f5" # PWA splash background; defaults to a sensible neutral + + # Phase 3 (v0.5) page-level opt-ins: + share = true # render share button (Web Share API + clipboard fallback) + qr = true # render collapsible QR code of this page Permalink + # vcard = true # serve /vcard.vcf and render a "Save contact" link + + # Phase 4 (v0.5) opt-in click analytics — Google Analytics 4. + # GA4 sets cookies — GDPR/PECR/CPRA consent is the site operator's responsibility. + # See docs/deployment-guide.md for the consent caveat. + # [params.analytics] + # measurementId = "G-XXXXXXXXXX" + # trackClicks = true # auto-emit data-analytics-event="link:" per link + # utmSource = "bio" + # utmMedium = "bio" + # # utmCampaign = "summer-launch" + +# Register VCard output format on home page (only needed when vcard = true) +# [outputs] +# home = ["HTML", "RSS", "VCard"] +# [outputFormats.VCard] +# mediaType = "text/vcard" +# baseName = "vcard" +# isPlainText = true +# notAlternative = true + [[params.links]] title = "GitHub" url = "https://github.com/tiennm99" diff --git a/exampleSite/static/_headers b/exampleSite/static/_headers new file mode 100644 index 0000000..b5f1502 --- /dev/null +++ b/exampleSite/static/_headers @@ -0,0 +1,24 @@ +/* + X-Content-Type-Options: nosniff + X-Frame-Options: SAMEORIGIN + Referrer-Policy: strict-origin-when-cross-origin + Permissions-Policy: camera=(), microphone=(), geolocation=() + Content-Security-Policy: default-src 'self'; img-src 'self' data: https:; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; connect-src 'self' https://www.google-analytics.com https://*.analytics.google.com https://*.google-analytics.com https://*.googletagmanager.com; font-src 'self'; frame-ancestors 'self' + +/css/* + Cache-Control: public, max-age=31536000, immutable + +/js/* + Cache-Control: public, max-age=31536000, immutable + +/icons/* + Cache-Control: public, max-age=31536000, immutable + +/images/* + Cache-Control: public, max-age=31536000, immutable + +/index.html + Cache-Control: public, max-age=3600, must-revalidate + +/ + Cache-Control: public, max-age=3600, must-revalidate diff --git a/exampleSite/vercel.json b/exampleSite/vercel.json new file mode 100644 index 0000000..6142740 --- /dev/null +++ b/exampleSite/vercel.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "cleanUrls": true, + "trailingSlash": false, + "headers": [ + { + "source": "/(.*)", + "headers": [ + { "key": "X-Content-Type-Options", "value": "nosniff" }, + { "key": "X-Frame-Options", "value": "SAMEORIGIN" }, + { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }, + { "key": "Permissions-Policy", "value": "camera=(), microphone=(), geolocation=()" }, + { "key": "Content-Security-Policy", "value": "default-src 'self'; img-src 'self' data: https:; script-src 'self' 'unsafe-inline' https://www.googletagmanager.com; style-src 'self' 'unsafe-inline'; connect-src 'self' https://www.google-analytics.com https://*.analytics.google.com https://*.google-analytics.com https://*.googletagmanager.com; font-src 'self'; frame-ancestors 'self'" } + ] + }, + { + "source": "/css/(.*)", + "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }] + }, + { + "source": "/js/(.*)", + "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }] + }, + { + "source": "/icons/(.*)", + "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }] + }, + { + "source": "/images/(.*)", + "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }] + } + ] +}