mirror of
https://github.com/jakejarvis/jarv.is.git
synced 2025-04-26 17:48:30 -04:00
rm middleware.ts 👋
This commit is contained in:
parent
444f91b6dc
commit
50c184fb21
@ -11,7 +11,7 @@ const Analytics = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Script
|
<Script
|
||||||
src="/_stream/u/script.js" // see middleware rewrite
|
src="/_stream/u/script.js" // see next.config.ts rewrite
|
||||||
id="umami-js"
|
id="umami-js"
|
||||||
strategy="afterInteractive"
|
strategy="afterInteractive"
|
||||||
data-website-id={process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID}
|
data-website-id={process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID}
|
||||||
|
@ -33,9 +33,6 @@ export const sendMessage = async (prevState: ContactState, formData: FormData):
|
|||||||
try {
|
try {
|
||||||
const data = v.safeParse(ContactSchema, Object.fromEntries(formData));
|
const data = v.safeParse(ContactSchema, Object.fromEntries(formData));
|
||||||
|
|
||||||
// send raw valibot result to Sentry for debugging
|
|
||||||
Sentry.captureMessage(JSON.stringify(data), "debug");
|
|
||||||
|
|
||||||
if (!data.success) {
|
if (!data.success) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
|
@ -16,11 +16,11 @@ const ContactForm = () => {
|
|||||||
message: "",
|
message: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [formFields, setFormFields] = useState<ContactInput>({
|
// keep track of input so we can repopulate the fields if the form fails
|
||||||
|
const [formFields, setFormFields] = useState<Partial<ContactInput>>({
|
||||||
name: "",
|
name: "",
|
||||||
email: "",
|
email: "",
|
||||||
message: "",
|
message: "",
|
||||||
"cf-turnstile-response": "",
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -29,44 +29,43 @@ const ContactForm = () => {
|
|||||||
type="text"
|
type="text"
|
||||||
name="name"
|
name="name"
|
||||||
placeholder="Name"
|
placeholder="Name"
|
||||||
// required
|
|
||||||
value={formFields.name}
|
value={formFields.name}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setFormFields({ ...formFields, name: e.target.value });
|
setFormFields({ ...formFields, name: e.target.value });
|
||||||
}}
|
}}
|
||||||
disabled={pending || formState.success}
|
disabled={pending || formState.success}
|
||||||
className={clsx(styles.input, formState.errors?.name && styles.invalid)}
|
className={clsx(styles.input, !pending && formState.errors?.name && styles.invalid)}
|
||||||
/>
|
/>
|
||||||
{formState.errors?.name && <span className={styles.fieldError}>{formState.errors.name[0]}</span>}
|
{!pending && formState.errors?.name && <span className={styles.fieldError}>{formState.errors.name[0]}</span>}
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="email"
|
type="email"
|
||||||
name="email"
|
name="email"
|
||||||
placeholder="Email"
|
placeholder="Email"
|
||||||
// required
|
|
||||||
inputMode="email"
|
inputMode="email"
|
||||||
value={formFields.email}
|
value={formFields.email}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setFormFields({ ...formFields, email: e.target.value });
|
setFormFields({ ...formFields, email: e.target.value });
|
||||||
}}
|
}}
|
||||||
disabled={pending || formState.success}
|
disabled={pending || formState.success}
|
||||||
className={clsx(styles.input, formState.errors?.email && styles.invalid)}
|
className={clsx(styles.input, !pending && formState.errors?.email && styles.invalid)}
|
||||||
/>
|
/>
|
||||||
{formState.errors?.email && <span className={styles.fieldError}>{formState.errors.email[0]}</span>}
|
{!pending && formState.errors?.email && <span className={styles.fieldError}>{formState.errors.email[0]}</span>}
|
||||||
|
|
||||||
<TextareaAutosize
|
<TextareaAutosize
|
||||||
name="message"
|
name="message"
|
||||||
placeholder="Write something..."
|
placeholder="Write something..."
|
||||||
minRows={5}
|
minRows={5}
|
||||||
// required
|
|
||||||
value={formFields.message}
|
value={formFields.message}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setFormFields({ ...formFields, message: e.target.value });
|
setFormFields({ ...formFields, message: e.target.value });
|
||||||
}}
|
}}
|
||||||
disabled={pending || formState.success}
|
disabled={pending || formState.success}
|
||||||
className={clsx(styles.input, styles.textarea, formState.errors?.message && styles.invalid)}
|
className={clsx(styles.input, styles.textarea, !pending && formState.errors?.message && styles.invalid)}
|
||||||
/>
|
/>
|
||||||
{formState.errors?.message && <span className={styles.fieldError}>{formState.errors.message[0]}</span>}
|
{!pending && formState.errors?.message && (
|
||||||
|
<span className={styles.fieldError}>{formState.errors.message[0]}</span>
|
||||||
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
@ -106,12 +105,12 @@ const ContactForm = () => {
|
|||||||
<div style={{ margin: "1em 0" }}>
|
<div style={{ margin: "1em 0" }}>
|
||||||
<Turnstile sitekey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || "1x00000000000000000000AA"} fixedSize />
|
<Turnstile sitekey={process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY || "1x00000000000000000000AA"} fixedSize />
|
||||||
</div>
|
</div>
|
||||||
{formState.errors?.["cf-turnstile-response"] && (
|
{!pending && formState.errors?.["cf-turnstile-response"] && (
|
||||||
<span className={styles.fieldError}>{formState.errors["cf-turnstile-response"][0]}</span>
|
<span className={styles.fieldError}>{formState.errors["cf-turnstile-response"][0]}</span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={styles.actionRow}>
|
<div className={styles.actionRow}>
|
||||||
{!formState?.success && (
|
{!formState.success && (
|
||||||
<button type="submit" disabled={pending} className={styles.submitButton}>
|
<button type="submit" disabled={pending} className={styles.submitButton}>
|
||||||
{pending ? (
|
{pending ? (
|
||||||
<span>Sending...</span>
|
<span>Sending...</span>
|
||||||
@ -131,7 +130,7 @@ const ContactForm = () => {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
{formState.message && (
|
{!pending && formState.message && (
|
||||||
<div className={clsx(styles.result, formState.success ? styles.success : styles.error)}>
|
<div className={clsx(styles.result, formState.success ? styles.success : styles.error)}>
|
||||||
{formState.success ? (
|
{formState.success ? (
|
||||||
<CheckIcon size="1.3em" className={styles.resultIcon} />
|
<CheckIcon size="1.3em" className={styles.resultIcon} />
|
||||||
|
@ -7,7 +7,7 @@ const robots = (): MetadataRoute.Robots => ({
|
|||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
userAgent: "*",
|
userAgent: "*",
|
||||||
disallow: ["/_stream/", "/_otel/", "/api/", "/404", "/500"],
|
disallow: ["/_stream/", "/api/", "/pubkey.asc", "/404", "/500"],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
sitemap: `${BASE_URL}/sitemap.xml`,
|
sitemap: `${BASE_URL}/sitemap.xml`,
|
||||||
|
@ -1,66 +0,0 @@
|
|||||||
import { NextResponse } from "next/server";
|
|
||||||
import type { NextRequest, MiddlewareConfig } from "next/server";
|
|
||||||
|
|
||||||
// assign "short codes" to approved reverse proxy destinations. for example:
|
|
||||||
// ["abc", "https://jakejarvis.github.io"] => /_stream/abc/123.html -> https://jakejarvis.github.io/123.html
|
|
||||||
const rewritePrefix = "/_stream/";
|
|
||||||
const rewrites = new Map();
|
|
||||||
|
|
||||||
// umami backend, see https://umami.is/docs/guides/running-on-vercel#proxy-umami-analytics-via-vercel
|
|
||||||
if (process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID) {
|
|
||||||
rewrites.set("u", process.env.NEXT_PUBLIC_UMAMI_URL || "https://cloud.umami.is");
|
|
||||||
}
|
|
||||||
|
|
||||||
export const middleware = (request: NextRequest) => {
|
|
||||||
const headers = new Headers();
|
|
||||||
|
|
||||||
// https://community.torproject.org/onion-services/advanced/onion-location/
|
|
||||||
if (process.env.NEXT_PUBLIC_ONION_DOMAIN) {
|
|
||||||
const onionUrl = request.nextUrl.clone();
|
|
||||||
onionUrl.hostname = process.env.NEXT_PUBLIC_ONION_DOMAIN;
|
|
||||||
onionUrl.protocol = "http";
|
|
||||||
onionUrl.port = "";
|
|
||||||
|
|
||||||
headers.set("onion-location", onionUrl.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
// debugging 🥛
|
|
||||||
headers.set("x-got-milk", "2%");
|
|
||||||
|
|
||||||
if (request.nextUrl.pathname.startsWith(rewritePrefix)) {
|
|
||||||
// extract the short code
|
|
||||||
const pathAfterPrefix = request.nextUrl.pathname.slice(rewritePrefix.length);
|
|
||||||
const slashIndex = pathAfterPrefix.indexOf("/");
|
|
||||||
const key = slashIndex === -1 ? pathAfterPrefix : pathAfterPrefix.slice(0, slashIndex);
|
|
||||||
|
|
||||||
// search the rewrite map for the short code
|
|
||||||
const proxiedOrigin = rewrites.get(key);
|
|
||||||
|
|
||||||
if (proxiedOrigin) {
|
|
||||||
// it's now safe to build the rewritten URL
|
|
||||||
const proxiedPath = slashIndex === -1 ? "/" : pathAfterPrefix.slice(slashIndex);
|
|
||||||
const proxiedUrl = new URL(`${proxiedPath}${request.nextUrl.search}`, proxiedOrigin);
|
|
||||||
|
|
||||||
headers.set("x-rewrite-url", proxiedUrl.toString());
|
|
||||||
|
|
||||||
// finally do the rewriting
|
|
||||||
return NextResponse.rewrite(proxiedUrl, {
|
|
||||||
request,
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we've gotten this far, continue normally to next.js
|
|
||||||
return NextResponse.next({
|
|
||||||
request,
|
|
||||||
headers,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const config: MiddlewareConfig = {
|
|
||||||
// save compute time by skipping middleware for next.js internals and static files
|
|
||||||
matcher: [
|
|
||||||
"/((?!_next/static|_next/image|_vercel|_otel|api|\\.well-known|[^?]*\\.(?:png|jpe?g|gif|webp|avif|svg|ico|webm|mp4|ttf|woff2?|xml|atom|txt|pdf|webmanifest)).*)",
|
|
||||||
],
|
|
||||||
};
|
|
@ -5,12 +5,6 @@ import { visit } from "unist-util-visit";
|
|||||||
import * as mdxPlugins from "./lib/helpers/remark-rehype-plugins";
|
import * as mdxPlugins from "./lib/helpers/remark-rehype-plugins";
|
||||||
import type { NextConfig } from "next";
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
type NextPlugin = (
|
|
||||||
config: NextConfig,
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
options?: any
|
|
||||||
) => NextConfig;
|
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
productionBrowserSourceMaps: true,
|
productionBrowserSourceMaps: true,
|
||||||
@ -60,10 +54,19 @@ const nextConfig: NextConfig = {
|
|||||||
serverSourceMaps: true,
|
serverSourceMaps: true,
|
||||||
},
|
},
|
||||||
eslint: {
|
eslint: {
|
||||||
// https://nextjs.org/docs/basic-features/eslint#linting-custom-directories-and-files
|
|
||||||
dirs: ["app", "components", "contexts", "hooks", "lib", "notes"],
|
dirs: ["app", "components", "contexts", "hooks", "lib", "notes"],
|
||||||
},
|
},
|
||||||
headers: async () => [
|
headers: async () => [
|
||||||
|
{
|
||||||
|
// matches any path without a file extension (aka period) or an underscore (e.g. /_next/image)
|
||||||
|
source: "/:path([^._]*)",
|
||||||
|
headers: [
|
||||||
|
{
|
||||||
|
key: "onion-location",
|
||||||
|
value: `http://${process.env.NEXT_PUBLIC_ONION_DOMAIN}/:path`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
source: "/pubkey.asc",
|
source: "/pubkey.asc",
|
||||||
headers: [
|
headers: [
|
||||||
@ -75,6 +78,11 @@ const nextConfig: NextConfig = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
rewrites: async () => [
|
rewrites: async () => [
|
||||||
|
{
|
||||||
|
// https://umami.is/docs/guides/running-on-vercel#proxy-umami-analytics-via-vercel
|
||||||
|
source: "/_stream/u/:path*",
|
||||||
|
destination: `${process.env.NEXT_PUBLIC_UMAMI_URL || "https://cloud.umami.is"}/:path*`,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// https://github.com/jakejarvis/tweets
|
// https://github.com/jakejarvis/tweets
|
||||||
source: "/tweets/:path*",
|
source: "/tweets/:path*",
|
||||||
@ -150,8 +158,10 @@ const nextConfig: NextConfig = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// my own macgyvered version of next-compose-plugins (RIP)
|
// my own macgyvered version of next-compose-plugins (RIP)
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
const nextPlugins: Array<
|
||||||
const nextPlugins: Array<NextPlugin | [NextPlugin, any]> = [
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(config: NextConfig) => NextConfig | [(config: NextConfig) => NextConfig, any]
|
||||||
|
> = [
|
||||||
require("@next/bundle-analyzer")({
|
require("@next/bundle-analyzer")({
|
||||||
enabled: !!process.env.ANALYZE,
|
enabled: !!process.env.ANALYZE,
|
||||||
}),
|
}),
|
||||||
@ -208,13 +218,10 @@ const nextPlugins: Array<NextPlugin | [NextPlugin, any]> = [
|
|||||||
project: process.env.SENTRY_PROJECT,
|
project: process.env.SENTRY_PROJECT,
|
||||||
authToken: process.env.SENTRY_AUTH_TOKEN,
|
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||||
silent: !process.env.CI,
|
silent: !process.env.CI,
|
||||||
tunnelRoute: "/_otel", // ensure this path is included in middleware's negative config.matcher expression
|
tunnelRoute: "/_stream/otel",
|
||||||
widenClientFileUpload: true,
|
widenClientFileUpload: true,
|
||||||
disableLogger: true,
|
disableLogger: true,
|
||||||
telemetry: false,
|
telemetry: false,
|
||||||
autoInstrumentAppDirectory: true,
|
|
||||||
autoInstrumentServerFunctions: true,
|
|
||||||
autoInstrumentMiddleware: false,
|
|
||||||
bundleSizeOptimizations: {
|
bundleSizeOptimizations: {
|
||||||
excludeDebugStatements: true,
|
excludeDebugStatements: true,
|
||||||
},
|
},
|
||||||
@ -226,8 +233,8 @@ const nextPlugins: Array<NextPlugin | [NextPlugin, any]> = [
|
|||||||
export default (): NextConfig =>
|
export default (): NextConfig =>
|
||||||
nextPlugins.reduce((acc, next) => {
|
nextPlugins.reduce((acc, next) => {
|
||||||
if (Array.isArray(next)) {
|
if (Array.isArray(next)) {
|
||||||
return (next[0] as NextPlugin)(acc, next[1]);
|
return next[0](acc, next[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (next as NextPlugin)(acc);
|
return next(acc);
|
||||||
}, nextConfig);
|
}, nextConfig);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user