diff --git a/app/contact/actions.ts b/app/contact/actions.ts
index 43470a0c..07b066eb 100644
--- a/app/contact/actions.ts
+++ b/app/contact/actions.ts
@@ -7,10 +7,35 @@ import * as Sentry from "@sentry/nextjs";
import * as config from "../../lib/config";
const ContactSchema = v.object({
- name: v.pipe(v.string(), v.nonEmpty("Your name is required.")),
- email: v.pipe(v.string(), v.nonEmpty("Your email address is required."), v.email("Invalid email address.")),
- message: v.pipe(v.string(), v.nonEmpty("A message is required.")),
- "cf-turnstile-response": v.pipe(v.string(), v.nonEmpty("Just do the stinkin CAPTCHA! 🤖")),
+ // TODO: replace duplicate error messages with v.message() when released. see:
+ // https://valibot.dev/api/message/
+ // https://github.com/fabian-hiller/valibot/blob/main/library/src/methods/message/message.ts
+ name: v.pipe(v.string("Your name is required."), v.trim(), v.nonEmpty("Your name is required.")),
+ email: v.pipe(
+ v.string("Your email address is required."),
+ v.trim(),
+ v.nonEmpty("Your email address is required."),
+ v.email("Invalid email address.")
+ ),
+ message: v.pipe(
+ v.string("A message is required."),
+ v.trim(),
+ v.nonEmpty("A message is required."),
+ v.minLength(10, "Your message must be at least 10 characters.")
+ ),
+ "cf-turnstile-response": v.pipe(
+ // token wasn't submitted at _all_, most likely a direct POST request by a spam bot
+ v.string("Shoo, bot."),
+ // form submitted properly but token was missing, might be a forgetful human
+ v.nonEmpty("Just do the stinkin CAPTCHA, human! 🤖"),
+ // very rudimentary length check based on Cloudflare's docs
+ // https://developers.cloudflare.com/turnstile/troubleshooting/testing/
+ v.minLength("XXXX.DUMMY.TOKEN.XXXX".length),
+ // "A Turnstile token can have up to 2048 characters."
+ // https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
+ v.maxLength(2048),
+ v.readonly()
+ ),
});
export type ContactInput = v.InferInput;
diff --git a/app/contact/page.tsx b/app/contact/page.tsx
index 879b81f6..3ea7d1ce 100644
--- a/app/contact/page.tsx
+++ b/app/contact/page.tsx
@@ -28,7 +28,7 @@ const Page = () => {
🔐 You can grab my public key here:{" "}
-
+
6BF3 79D3 6F67 1480 2B0C 9CF2 51E6 9A39
diff --git a/app/notes/[slug]/counter.tsx b/app/notes/[slug]/counter.tsx
index b54d2d30..a7fdfbc3 100644
--- a/app/notes/[slug]/counter.tsx
+++ b/app/notes/[slug]/counter.tsx
@@ -23,6 +23,8 @@ const HitCounter = async ({ slug }: { slug: string }) => {
);
} catch (error) {
Sentry.captureException(error);
+
+ return ?;
}
};
diff --git a/app/page.tsx b/app/page.tsx
index 3fb32aa3..5e3572bf 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -258,13 +258,12 @@ const Page = () => {
{" "}
{" "}
=> {
accept: "application/vnd.github.v3+json",
authorization: `token ${process.env.GITHUB_TOKEN}`,
},
+ request: {
+ // override fetch() to use next's extension to cache the response
+ // https://nextjs.org/docs/app/api-reference/functions/fetch#fetchurl-options
+ fetch: (url: string | URL | Request, options?: RequestInit) => {
+ return fetch(url, {
+ ...options,
+ cache: "force-cache",
+ next: {
+ // 10 minutes
+ revalidate: 600,
+ },
+ });
+ },
+ },
}
);
diff --git a/app/robots.ts b/app/robots.ts
index ebd770f0..5b2a3f7f 100644
--- a/app/robots.ts
+++ b/app/robots.ts
@@ -7,7 +7,7 @@ const robots = (): MetadataRoute.Robots => ({
rules: [
{
userAgent: "*",
- disallow: ["/_stream/", "/api/", "/pubkey.asc", "/404", "/500"],
+ disallow: ["/_stream/", "/api/", "/404", "/500"],
},
],
sitemap: `${BASE_URL}/sitemap.xml`,
diff --git a/components/Gist/Gist.tsx b/components/Gist/Gist.tsx
index 4963ec5d..be2d3d44 100644
--- a/components/Gist/Gist.tsx
+++ b/components/Gist/Gist.tsx
@@ -9,7 +9,13 @@ const Gist = async ({ id, file }: GistProps) => {
const iframeId = `gist-${id}${file ? `-${file}` : ""}`;
const scriptUrl = `https://gist.github.com/${id}.js${file ? `?file=${file}` : ""}`;
- const scriptResponse = await fetch(scriptUrl);
+ const scriptResponse = await fetch(scriptUrl, {
+ cache: "force-cache",
+ next: {
+ // cache indefinitely in data store
+ revalidate: 0,
+ },
+ });
if (!scriptResponse.ok) {
console.warn(`[gist] failed to fetch js:`, scriptResponse.statusText);
diff --git a/components/YouTube/YouTube.module.css b/components/YouTube/YouTube.module.css
deleted file mode 100644
index 5961175c..00000000
--- a/components/YouTube/YouTube.module.css
+++ /dev/null
@@ -1,4 +0,0 @@
-/* stylelint-disable-next-line selector-type-no-unknown */
-.wrapper lite-youtube {
- margin: 0 auto;
-}
diff --git a/components/YouTube/YouTube.tsx b/components/YouTube/YouTube.tsx
index 54108e7f..8fab3c98 100644
--- a/components/YouTube/YouTube.tsx
+++ b/components/YouTube/YouTube.tsx
@@ -1,17 +1,14 @@
-import { YouTubeEmbed } from "@next/third-parties/google";
+"use client";
-import styles from "./YouTube.module.css";
+import YouTubeEmbed from "react-lite-youtube-embed";
+import type { ComponentPropsWithoutRef } from "react";
-export type YouTubeProps = {
- id: string;
-};
+import "react-lite-youtube-embed/dist/LiteYouTubeEmbed.css";
-const YouTube = ({ id }: YouTubeProps) => {
- return (
-
-
-
- );
+export type YouTubeProps = Omit, "title">;
+
+const YouTube = ({ ...rest }: YouTubeProps) => {
+ return ;
};
export default YouTube;
diff --git a/instrumentation-client.ts b/instrumentation-client.ts
index 8e5ba2b3..57559894 100644
--- a/instrumentation-client.ts
+++ b/instrumentation-client.ts
@@ -2,7 +2,6 @@ import * as Sentry from "@sentry/nextjs";
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
- environment: process.env.NEXT_PUBLIC_VERCEL_ENV,
integrations: [Sentry.browserTracingIntegration(), Sentry.httpClientIntegration()],
tracesSampleRate: 1.0,
});
diff --git a/instrumentation.ts b/instrumentation.ts
index b52175bd..ed7d18e9 100644
--- a/instrumentation.ts
+++ b/instrumentation.ts
@@ -5,7 +5,6 @@ export const onRequestError = Sentry.captureRequestError;
export const register = () => {
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN || process.env.SENTRY_DSN,
- environment: process.env.NEXT_PUBLIC_VERCEL_ENV,
integrations: [Sentry.captureConsoleIntegration()],
tracesSampleRate: 1.0,
// https://docs.sentry.io/platforms/javascript/guides/nextjs/configuration/options/#normalizeDepth
diff --git a/next.config.ts b/next.config.ts
index a1c05266..4bb5dcc5 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -58,7 +58,23 @@ const nextConfig: NextConfig = {
},
headers: async () => [
{
- // matches any path without a file extension (aka period) or an underscore (e.g. /_next/image)
+ // matches any path
+ source: "/(.*)",
+ headers: [
+ {
+ key: "strict-transport-security",
+ value: "max-age=63072000; includeSubDomains; preload",
+ },
+ {
+ key: "x-got-milk",
+ value: "2%",
+ },
+ ],
+ },
+ {
+ // https://community.torproject.org/onion-services/advanced/onion-location/
+ // only needed on actual pages, not static assets, so make a best effort by matching any path **without** a file
+ // extension (aka a period) and/or an underscore (e.g. /_next/image).
source: "/:path([^._]*)",
headers: [
{
@@ -67,15 +83,6 @@ const nextConfig: NextConfig = {
},
],
},
- {
- source: "/pubkey.asc",
- headers: [
- {
- key: "Content-Type",
- value: "text/plain; charset=utf-8",
- },
- ],
- },
],
rewrites: async () => [
{
@@ -88,6 +95,11 @@ const nextConfig: NextConfig = {
source: "/tweets/:path*",
destination: "https://tweets-khaki.vercel.app/:path*",
},
+ {
+ source: "/pubkey.asc",
+ destination:
+ "https://keys.openpgp.org/pks/lookup?op=get&options=mr&search=0x3bc6e5776bf379d36f6714802b0c9cf251e69a39",
+ },
],
redirects: async () => [
{ source: "/y2k", destination: "https://y2k.pages.dev", permanent: false },
@@ -231,10 +243,4 @@ const nextPlugins: Array<
// eslint-disable-next-line import/no-anonymous-default-export
export default (): NextConfig =>
- nextPlugins.reduce((acc, next) => {
- if (Array.isArray(next)) {
- return next[0](acc, next[1]);
- }
-
- return next(acc);
- }, nextConfig);
+ nextPlugins.reduce((acc, plugin) => (Array.isArray(plugin) ? plugin[0](acc, plugin[1]) : plugin(acc)), nextConfig);
diff --git a/package.json b/package.json
index f015d757..fac11a74 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,6 @@
"@mdx-js/react": "^3.1.0",
"@next/bundle-analyzer": "15.3.0-canary.25",
"@next/mdx": "15.3.0-canary.25",
- "@next/third-parties": "15.3.0-canary.25",
"@octokit/graphql": "^8.2.1",
"@octokit/graphql-schema": "^15.26.0",
"@sentry/nextjs": "^9.10.1",
@@ -37,7 +36,7 @@
"fast-glob": "^3.3.3",
"feed": "^4.2.2",
"geist": "^1.3.1",
- "html-entities": "^2.5.5",
+ "html-entities": "^2.6.0",
"lucide-react": "0.485.0",
"modern-normalize": "^3.0.1",
"next": "15.3.0-canary.25",
@@ -49,8 +48,9 @@
"react-dom": "19.1.0",
"react-innertext": "^1.1.5",
"react-is": "19.1.0",
+ "react-lite-youtube-embed": "^2.4.0",
"react-schemaorg": "^2.0.0",
- "react-textarea-autosize": "^8.5.8",
+ "react-textarea-autosize": "^8.5.9",
"react-timeago": "^8.0.0",
"react-turnstile": "^1.1.4",
"react-tweet": "^3.2.2",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index a7c460bf..28478a7f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -32,9 +32,6 @@ importers:
'@next/mdx':
specifier: 15.3.0-canary.25
version: 15.3.0-canary.25(@mdx-js/loader@3.1.0(acorn@8.14.1)(webpack@5.98.0))(@mdx-js/react@3.1.0(@types/react@19.0.12)(react@19.1.0))
- '@next/third-parties':
- specifier: 15.3.0-canary.25
- version: 15.3.0-canary.25(next@15.3.0-canary.25(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-aeaed83-20250323)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
'@octokit/graphql':
specifier: ^8.2.1
version: 8.2.1
@@ -69,8 +66,8 @@ importers:
specifier: ^1.3.1
version: 1.3.1(next@15.3.0-canary.25(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-aeaed83-20250323)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))
html-entities:
- specifier: ^2.5.5
- version: 2.5.5
+ specifier: ^2.6.0
+ version: 2.6.0
lucide-react:
specifier: 0.485.0
version: 0.485.0(react@19.1.0)
@@ -104,12 +101,15 @@ importers:
react-is:
specifier: 19.1.0
version: 19.1.0
+ react-lite-youtube-embed:
+ specifier: ^2.4.0
+ version: 2.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
react-schemaorg:
specifier: ^2.0.0
version: 2.0.0(react@19.1.0)(schema-dts@1.1.5)(typescript@5.8.2)
react-textarea-autosize:
- specifier: ^8.5.8
- version: 8.5.8(@types/react@19.0.12)(react@19.1.0)
+ specifier: ^8.5.9
+ version: 8.5.9(@types/react@19.0.12)(react@19.1.0)
react-timeago:
specifier: ^8.0.0
version: 8.0.0(react@19.1.0)
@@ -728,12 +728,6 @@ packages:
cpu: [x64]
os: [win32]
- '@next/third-parties@15.3.0-canary.25':
- resolution: {integrity: sha512-UiwiSeKKyBW31YZ6v4QEb9+Sx22ubWcf/74Jv1dIQnXrKLMd3OTNj9r0Z3Bj5sIpuBx0NIf8LCr90H2sJRRYDw==}
- peerDependencies:
- next: ^13.0.0 || ^14.0.0 || ^15.0.0
- react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
-
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -2687,8 +2681,8 @@ packages:
resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==}
engines: {node: ^16.14.0 || >=18.0.0}
- html-entities@2.5.5:
- resolution: {integrity: sha512-24CG9o869vSa86BGCf7x65slrAztzFTU5VBQzEIwqjhKuB4zCC7xlH/7NCcZ1EN5MdmGx9lUqugfutuT6J+jKQ==}
+ html-entities@2.6.0:
+ resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==}
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
@@ -3702,6 +3696,12 @@ packages:
react-is@19.1.0:
resolution: {integrity: sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==}
+ react-lite-youtube-embed@2.4.0:
+ resolution: {integrity: sha512-Xo6cM1zPlROvvM97JkqQIoXstlQDaC4+DawmM7BB7Hh1cXrkBHEGq1iJlQxBTUWAUklmpcC7ph7qg7CztXtABQ==}
+ peerDependencies:
+ react: '>=18.2.0'
+ react-dom: '>=18.2.0'
+
react-promise-suspense@0.3.4:
resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==}
@@ -3713,8 +3713,8 @@ packages:
schema-dts: '>=0.7.4'
typescript: '>=3.1.6'
- react-textarea-autosize@8.5.8:
- resolution: {integrity: sha512-iUiIj70JefrTuSJ4LbVFiSqWiHHss5L63L717bqaWHMgkm9sz6eEvro4vZ3uQfGJbevzwT6rHOszHKA8RkhRMg==}
+ react-textarea-autosize@8.5.9:
+ resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==}
engines: {node: '>=10'}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
@@ -4246,9 +4246,6 @@ packages:
engines: {node: '>=10'}
hasBin: true
- third-party-capital@1.0.20:
- resolution: {integrity: sha512-oB7yIimd8SuGptespDAZnNkzIz+NWaJCu2RMsbs4Wmp9zSDUM8Nhi3s2OOcqYuv3mN4hitXc8DVx+LyUmbUDiA==}
-
tinyglobby@0.2.12:
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
engines: {node: '>=12.0.0'}
@@ -5081,12 +5078,6 @@ snapshots:
'@next/swc-win32-x64-msvc@15.3.0-canary.25':
optional: true
- '@next/third-parties@15.3.0-canary.25(next@15.3.0-canary.25(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-aeaed83-20250323)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)':
- dependencies:
- next: 15.3.0-canary.25(@babel/core@7.26.10)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.0.0-beta-aeaed83-20250323)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
- react: 19.1.0
- third-party-capital: 1.0.20
-
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -7460,7 +7451,7 @@ snapshots:
dependencies:
lru-cache: 10.4.3
- html-entities@2.5.5: {}
+ html-entities@2.6.0: {}
html-escaper@2.0.2: {}
@@ -8727,6 +8718,11 @@ snapshots:
react-is@19.1.0: {}
+ react-lite-youtube-embed@2.4.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
react-promise-suspense@0.3.4:
dependencies:
fast-deep-equal: 2.0.1
@@ -8737,7 +8733,7 @@ snapshots:
schema-dts: 1.1.5
typescript: 5.8.2
- react-textarea-autosize@8.5.8(@types/react@19.0.12)(react@19.1.0):
+ react-textarea-autosize@8.5.9(@types/react@19.0.12)(react@19.1.0):
dependencies:
'@babel/runtime': 7.27.0
react: 19.1.0
@@ -9527,8 +9523,6 @@ snapshots:
commander: 2.20.3
source-map-support: 0.5.21
- third-party-capital@1.0.20: {}
-
tinyglobby@0.2.12:
dependencies:
fdir: 6.4.3(picomatch@4.0.2)
diff --git a/public/pubkey.asc b/public/pubkey.asc
deleted file mode 100644
index fd63d34d..00000000
--- a/public/pubkey.asc
+++ /dev/null
@@ -1,64 +0,0 @@
------BEGIN PGP PUBLIC KEY BLOCK-----
-
-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
-=sqOH
------END PGP PUBLIC KEY BLOCK-----