1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-04-26 01:45:25 -04:00

clean up remaining NEXT_PUBLIC_ environment variables

This commit is contained in:
Jake Jarvis 2025-04-13 16:28:00 -04:00
parent f08aa16862
commit b60fbcc15c
Signed by: jake
SSH Key Fingerprint: SHA256:nCkvAjYA6XaSPUqc4TfbBQTpzr8Xj7ritg/sGInCdkc
21 changed files with 137 additions and 117 deletions

View File

@ -10,7 +10,11 @@ RESEND_API_KEY=
RESEND_FROM_EMAIL= RESEND_FROM_EMAIL=
RESEND_TO_EMAIL= RESEND_TO_EMAIL=
TURNSTILE_SECRET_KEY= TURNSTILE_SECRET_KEY=
NEXT_PUBLIC_BASE_URL=
NEXT_PUBLIC_GISCUS_CATEGORY_ID= NEXT_PUBLIC_GISCUS_CATEGORY_ID=
NEXT_PUBLIC_GISCUS_REPO_ID= NEXT_PUBLIC_GISCUS_REPO_ID=
NEXT_PUBLIC_GITHUB_REPO=
NEXT_PUBLIC_ONION_DOMAIN= NEXT_PUBLIC_ONION_DOMAIN=
NEXT_PUBLIC_SITE_LOCALE=
NEXT_PUBLIC_SITE_TZ=
NEXT_PUBLIC_TURNSTILE_SITE_KEY= NEXT_PUBLIC_TURNSTILE_SITE_KEY=

View File

@ -1,8 +1,8 @@
import { env } from "../../lib/env";
import { JsonLd } from "react-schemaorg"; import { JsonLd } from "react-schemaorg";
import PageTitle from "../../components/PageTitle"; import PageTitle from "../../components/PageTitle";
import Video from "../../components/Video"; import Video from "../../components/Video";
import { addMetadata } from "../../lib/helpers/metadata"; import { addMetadata } from "../../lib/helpers/metadata";
import { BASE_URL } from "../../lib/config/constants";
import type { VideoObject } from "schema-dts"; import type { VideoObject } from "schema-dts";
import mp4 from "./birthday.mp4"; import mp4 from "./birthday.mp4";
@ -26,9 +26,9 @@ const Page = () => {
"@type": "VideoObject", "@type": "VideoObject",
name: metadata.title as string, name: metadata.title as string,
description: metadata.description as string, description: metadata.description as string,
contentUrl: `${BASE_URL}${webm}`, contentUrl: `${env.NEXT_PUBLIC_BASE_URL}${webm}`,
thumbnailUrl: `${BASE_URL}${thumbnail.src}`, thumbnailUrl: `${env.NEXT_PUBLIC_BASE_URL}${thumbnail.src}`,
embedUrl: `${BASE_URL}/birthday`, embedUrl: `${env.NEXT_PUBLIC_BASE_URL}/birthday`,
uploadDate: "1996-02-06T00:00:00Z", uploadDate: "1996-02-06T00:00:00Z",
duration: "PT6M10S", duration: "PT6M10S",
}} }}

View File

@ -1,9 +1,9 @@
import { env } from "../../lib/env";
import { JsonLd } from "react-schemaorg"; import { JsonLd } from "react-schemaorg";
import PageTitle from "../../components/PageTitle"; import PageTitle from "../../components/PageTitle";
import Link from "../../components/Link"; import Link from "../../components/Link";
import Video from "../../components/Video"; import Video from "../../components/Video";
import { addMetadata } from "../../lib/helpers/metadata"; import { addMetadata } from "../../lib/helpers/metadata";
import { BASE_URL } from "../../lib/config/constants";
import type { VideoObject } from "schema-dts"; import type { VideoObject } from "schema-dts";
import webm from "./convention.webm"; import webm from "./convention.webm";
@ -28,9 +28,9 @@ const Page = () => {
"@type": "VideoObject", "@type": "VideoObject",
name: metadata.title as string, name: metadata.title as string,
description: metadata.description as string, description: metadata.description as string,
contentUrl: `${BASE_URL}${webm}`, contentUrl: `${env.NEXT_PUBLIC_BASE_URL}${webm}`,
thumbnailUrl: `${BASE_URL}${thumbnail.src}`, thumbnailUrl: `${env.NEXT_PUBLIC_BASE_URL}${thumbnail.src}`,
embedUrl: `${BASE_URL}/hillary`, embedUrl: `${env.NEXT_PUBLIC_BASE_URL}/hillary`,
uploadDate: "2016-07-25T00:00:00Z", uploadDate: "2016-07-25T00:00:00Z",
duration: "PT1M51S", duration: "PT1M51S",
}} }}

View File

@ -1,3 +1,4 @@
import { env } from "../lib/env";
import { JsonLd } from "react-schemaorg"; import { JsonLd } from "react-schemaorg";
import { Analytics } from "@vercel/analytics/next"; import { Analytics } from "@vercel/analytics/next";
import clsx from "clsx"; import clsx from "clsx";
@ -7,7 +8,7 @@ import Footer from "../components/Footer";
import { SkipNavLink, SkipNavTarget } from "../components/SkipNav"; import { SkipNavLink, SkipNavTarget } from "../components/SkipNav";
import { defaultMetadata } from "../lib/helpers/metadata"; import { defaultMetadata } from "../lib/helpers/metadata";
import * as config from "../lib/config"; import * as config from "../lib/config";
import { BASE_URL, MAX_WIDTH, SITE_LOCALE } from "../lib/config/constants"; import { MAX_WIDTH } from "../lib/config/constants";
import type { Metadata } from "next"; import type { Metadata } from "next";
import type { Person, WebSite } from "schema-dts"; import type { Person, WebSite } from "schema-dts";
@ -21,7 +22,7 @@ export const metadata: Metadata = defaultMetadata;
const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => { const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
return ( return (
<html lang={SITE_LOCALE || "en-US"} suppressHydrationWarning> <html lang={env.NEXT_PUBLIC_SITE_LOCALE} suppressHydrationWarning>
<head> <head>
<ThemeScript /> <ThemeScript />
@ -29,12 +30,12 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
item={{ item={{
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "Person", "@type": "Person",
"@id": `${BASE_URL}/#person`, "@id": `${env.NEXT_PUBLIC_BASE_URL}/#person`,
name: config.authorName, name: config.authorName,
url: BASE_URL, url: env.NEXT_PUBLIC_BASE_URL,
image: [`${BASE_URL}/opengraph-image.jpg`], image: [`${env.NEXT_PUBLIC_BASE_URL}/opengraph-image.jpg`],
sameAs: [ sameAs: [
`${BASE_URL}`, env.NEXT_PUBLIC_BASE_URL!,
`https://${config.authorSocial?.mastodon}`, `https://${config.authorSocial?.mastodon}`,
`https://github.com/${config.authorSocial?.github}`, `https://github.com/${config.authorSocial?.github}`,
`https://bsky.app/profile/${config.authorSocial?.bluesky}`, `https://bsky.app/profile/${config.authorSocial?.bluesky}`,
@ -51,12 +52,12 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
item={{ item={{
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "WebSite", "@type": "WebSite",
"@id": `${BASE_URL}/#website`, "@id": `${env.NEXT_PUBLIC_BASE_URL}/#website`,
name: config.siteName, name: config.siteName,
url: BASE_URL, url: env.NEXT_PUBLIC_BASE_URL,
author: config.authorName, author: config.authorName,
description: config.description, description: config.description,
inLanguage: SITE_LOCALE, inLanguage: env.NEXT_PUBLIC_SITE_LOCALE,
license: config.licenseUrl, license: config.licenseUrl,
}} }}
/> />

View File

@ -1,9 +1,9 @@
import { env } from "../../lib/env";
import { JsonLd } from "react-schemaorg"; import { JsonLd } from "react-schemaorg";
import PageTitle from "../../components/PageTitle"; import PageTitle from "../../components/PageTitle";
import Link from "../../components/Link"; import Link from "../../components/Link";
import Video from "../../components/Video"; import Video from "../../components/Video";
import { addMetadata } from "../../lib/helpers/metadata"; import { addMetadata } from "../../lib/helpers/metadata";
import { BASE_URL } from "../../lib/config/constants";
import type { VideoObject } from "schema-dts"; import type { VideoObject } from "schema-dts";
import mp4 from "./leo.mp4"; import mp4 from "./leo.mp4";
@ -28,9 +28,9 @@ const Page = () => {
"@type": "VideoObject", "@type": "VideoObject",
name: metadata.title as string, name: metadata.title as string,
description: metadata.description as string, description: metadata.description as string,
contentUrl: `${BASE_URL}${webm}`, contentUrl: `${env.NEXT_PUBLIC_BASE_URL}${webm}`,
thumbnailUrl: `${BASE_URL}${thumbnail.src}`, thumbnailUrl: `${env.NEXT_PUBLIC_BASE_URL}${thumbnail.src}`,
embedUrl: `${BASE_URL}/leo`, embedUrl: `${env.NEXT_PUBLIC_BASE_URL}/leo`,
uploadDate: "2007-05-10T00:00:00Z", uploadDate: "2007-05-10T00:00:00Z",
duration: "PT1M48S", duration: "PT1M48S",
}} }}

View File

@ -1,5 +1,5 @@
import { env } from "../lib/env";
import * as config from "../lib/config"; import * as config from "../lib/config";
import { SITE_LOCALE } from "../lib/config/constants";
import type { MetadataRoute } from "next"; import type { MetadataRoute } from "next";
const manifest = (): MetadataRoute.Manifest => { const manifest = (): MetadataRoute.Manifest => {
@ -8,7 +8,7 @@ const manifest = (): MetadataRoute.Manifest => {
// eslint-disable-next-line camelcase // eslint-disable-next-line camelcase
short_name: config.siteName, short_name: config.siteName,
description: config.description, description: config.description,
lang: SITE_LOCALE, lang: env.NEXT_PUBLIC_SITE_LOCALE,
icons: [ icons: [
{ {
src: "/icon.png", src: "/icon.png",

View File

@ -1,7 +1,7 @@
import { env } from "../../../lib/env";
import { connection } from "next/server"; import { connection } from "next/server";
import CountUp from "../../../components/CountUp"; import CountUp from "../../../components/CountUp";
import redis from "../../../lib/redis"; import redis from "../../../lib/redis";
import { SITE_LOCALE } from "../../../lib/config/constants";
const HitCounter = async ({ slug }: { slug: string }) => { const HitCounter = async ({ slug }: { slug: string }) => {
await connection(); await connection();
@ -16,7 +16,7 @@ const HitCounter = async ({ slug }: { slug: string }) => {
// we have data! // we have data!
return ( return (
<span title={`${Intl.NumberFormat(SITE_LOCALE || "en-US").format(hits)} ${hits === 1 ? "view" : "views"}`}> <span title={`${Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(hits)} ${hits === 1 ? "view" : "views"}`}>
<CountUp start={0} end={hits} delay={0} duration={1.5} /> <CountUp start={0} end={hits} delay={0} duration={1.5} />
</span> </span>
); );

View File

@ -10,7 +10,7 @@ import HitCounter from "./counter";
import { getSlugs, getFrontMatter } from "../../../lib/helpers/posts"; import { getSlugs, getFrontMatter } from "../../../lib/helpers/posts";
import { addMetadata } from "../../../lib/helpers/metadata"; import { addMetadata } from "../../../lib/helpers/metadata";
import * as config from "../../../lib/config"; import * as config from "../../../lib/config";
import { BASE_URL, POSTS_DIR, SITE_LOCALE } from "../../../lib/config/constants"; import { POSTS_DIR } from "../../../lib/config/constants";
import { size as ogImageSize } from "./opengraph-image"; import { size as ogImageSize } from "./opengraph-image";
import type { Metadata } from "next"; import type { Metadata } from "next";
import type { BlogPosting } from "schema-dts"; import type { BlogPosting } from "schema-dts";
@ -72,18 +72,18 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
url: frontmatter!.permalink, url: frontmatter!.permalink,
image: { image: {
"@type": "ImageObject", "@type": "ImageObject",
contentUrl: `${BASE_URL}/${POSTS_DIR}/${frontmatter!.slug}/opengraph-image`, contentUrl: `${env.NEXT_PUBLIC_BASE_URL}/${POSTS_DIR}/${frontmatter!.slug}/opengraph-image`,
width: `${ogImageSize.width}`, width: `${ogImageSize.width}`,
height: `${ogImageSize.height}`, height: `${ogImageSize.height}`,
}, },
keywords: frontmatter!.tags?.join(", "), keywords: frontmatter!.tags?.join(", "),
datePublished: frontmatter!.date, datePublished: frontmatter!.date,
dateModified: frontmatter!.date, dateModified: frontmatter!.date,
inLanguage: SITE_LOCALE, inLanguage: env.NEXT_PUBLIC_SITE_LOCALE,
license: config.licenseUrl, license: config.licenseUrl,
author: { author: {
// defined in app/layout.tsx // defined in app/layout.tsx
"@id": `${BASE_URL}/#person`, "@id": `${env.NEXT_PUBLIC_BASE_URL}/#person`,
}, },
}} }}
/> />
@ -111,7 +111,7 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
<div className={styles.metaItem}> <div className={styles.metaItem}>
<Link <Link
href={`https://github.com/${config.githubRepo}/blob/main/${POSTS_DIR}/${frontmatter!.slug}/index.mdx`} href={`https://github.com/${env.NEXT_PUBLIC_GITHUB_REPO}/blob/main/${POSTS_DIR}/${frontmatter!.slug}/index.mdx`}
title={`Edit "${frontmatter!.title}" on GitHub`} title={`Edit "${frontmatter!.title}" on GitHub`}
plain plain
className={styles.metaLink} className={styles.metaLink}
@ -121,26 +121,23 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => {
</Link> </Link>
</div> </div>
{/* only count hits on production site */} <div
{env.VERCEL_ENV === "production" ? ( className={styles.metaItem}
<div style={{
className={styles.metaItem} // fix potential layout shift when number of hits loads
style={{ minWidth: "7em",
// fix potential layout shift when number of hits loads marginRight: 0,
minWidth: "7em", }}
marginRight: 0, >
}} <EyeIcon size="1.2em" className={styles.metaIcon} />
<Suspense
// when this loads, the component will count up from zero to the actual number of hits, so we can simply
// show a zero here as a "loading indicator"
fallback={<span>0</span>}
> >
<EyeIcon size="1.2em" className={styles.metaIcon} /> <HitCounter slug={`${POSTS_DIR}/${frontmatter!.slug}`} />
<Suspense </Suspense>
// when this loads, the component will count up from zero to the actual number of hits, so we can simply </div>
// show a zero here as a "loading indicator"
fallback={<span>0</span>}
>
<HitCounter slug={`${POSTS_DIR}/${frontmatter!.slug}`} />
</Suspense>
</div>
) : null}
</div> </div>
<h1 className={styles.title}> <h1 className={styles.title}>

View File

@ -7,7 +7,6 @@ import Link from "../../components/Link";
import RelativeTime from "../../components/RelativeTime"; import RelativeTime from "../../components/RelativeTime";
import { addMetadata } from "../../lib/helpers/metadata"; import { addMetadata } from "../../lib/helpers/metadata";
import * as config from "../../lib/config"; import * as config from "../../lib/config";
import { SITE_LOCALE } from "../../lib/config/constants";
import type { User } from "@octokit/graphql-schema"; import type { User } from "@octokit/graphql-schema";
import styles from "./page.module.css"; import styles from "./page.module.css";
@ -121,12 +120,12 @@ const Page = async () => {
<div className={styles.metaItem}> <div className={styles.metaItem}>
<Link <Link
href={`${repo!.url}/stargazers`} href={`${repo!.url}/stargazers`}
title={`${Intl.NumberFormat(SITE_LOCALE || "en-US").format(repo!.stargazerCount)} ${repo!.stargazerCount === 1 ? "star" : "stars"}`} title={`${Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(repo!.stargazerCount)} ${repo!.stargazerCount === 1 ? "star" : "stars"}`}
plain plain
className={styles.metaLink} className={styles.metaLink}
> >
<StarIcon size="1.25em" className={styles.metaIcon} /> <StarIcon size="1.25em" className={styles.metaIcon} />
<span>{Intl.NumberFormat(SITE_LOCALE || "en-US").format(repo!.stargazerCount)}</span> <span>{Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(repo!.stargazerCount)}</span>
</Link> </Link>
</div> </div>
)} )}
@ -135,12 +134,12 @@ const Page = async () => {
<div className={styles.metaItem}> <div className={styles.metaItem}>
<Link <Link
href={`${repo!.url}/network/members`} href={`${repo!.url}/network/members`}
title={`${Intl.NumberFormat(SITE_LOCALE || "en-US").format(repo!.forkCount)} ${repo!.forkCount === 1 ? "fork" : "forks"}`} title={`${Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(repo!.forkCount)} ${repo!.forkCount === 1 ? "fork" : "forks"}`}
plain plain
className={styles.metaLink} className={styles.metaLink}
> >
<GitForkIcon size="1.25em" className={styles.metaIcon} /> <GitForkIcon size="1.25em" className={styles.metaIcon} />
<span>{Intl.NumberFormat(SITE_LOCALE || "en-US").format(repo!.forkCount)}</span> <span>{Intl.NumberFormat(env.NEXT_PUBLIC_SITE_LOCALE).format(repo!.forkCount)}</span>
</Link> </Link>
</div> </div>
)} )}

View File

@ -1,4 +1,4 @@
import { BASE_URL } from "../lib/config/constants"; import { env } from "../lib/env";
import type { MetadataRoute } from "next"; import type { MetadataRoute } from "next";
const robots = (): MetadataRoute.Robots => ({ const robots = (): MetadataRoute.Robots => ({
@ -8,7 +8,7 @@ const robots = (): MetadataRoute.Robots => ({
disallow: ["/api/", "/404", "/500"], disallow: ["/api/", "/404", "/500"],
}, },
], ],
sitemap: `${BASE_URL}/sitemap.xml`, sitemap: `${env.NEXT_PUBLIC_BASE_URL}/sitemap.xml`,
}); });
export default robots; export default robots;

View File

@ -1,7 +1,7 @@
import { env } from "../lib/env";
import path from "path"; import path from "path";
import glob from "fast-glob"; import glob from "fast-glob";
import { getFrontMatter } from "../lib/helpers/posts"; import { getFrontMatter } from "../lib/helpers/posts";
import { BASE_URL, RELEASE_TIMESTAMP } from "../lib/config/constants";
import type { MetadataRoute } from "next"; import type { MetadataRoute } from "next";
const sitemap = async (): Promise<MetadataRoute.Sitemap> => { const sitemap = async (): Promise<MetadataRoute.Sitemap> => {
@ -9,9 +9,9 @@ const sitemap = async (): Promise<MetadataRoute.Sitemap> => {
const routes: MetadataRoute.Sitemap = [ const routes: MetadataRoute.Sitemap = [
{ {
// homepage // homepage
url: `${BASE_URL}`, url: `${env.NEXT_PUBLIC_BASE_URL}`,
priority: 1.0, priority: 1.0,
lastModified: new Date(RELEASE_TIMESTAMP), // timestamp frozen when a new build is deployed lastModified: new Date(),
}, },
]; ];
@ -30,7 +30,7 @@ const sitemap = async (): Promise<MetadataRoute.Sitemap> => {
).forEach((route) => { ).forEach((route) => {
routes.push({ routes.push({
// remove matching page.(tsx|mdx) file and make all URLs absolute // remove matching page.(tsx|mdx) file and make all URLs absolute
url: `${BASE_URL}/${route.replace(/\/page\.(tsx|mdx)$/, "")}`, url: `${env.NEXT_PUBLIC_BASE_URL}/${route.replace(/\/page\.(tsx|mdx)$/, "")}`,
}); });
}); });

View File

@ -2,7 +2,6 @@
import { env } from "../../lib/env"; import { env } from "../../lib/env";
import Giscus from "@giscus/react"; import Giscus from "@giscus/react";
import * as config from "../../lib/config";
import type { GiscusProps } from "@giscus/react"; import type { GiscusProps } from "@giscus/react";
export type CommentsProps = { export type CommentsProps = {
@ -21,7 +20,7 @@ const Comments = ({ title }: CommentsProps) => {
return ( return (
<Giscus <Giscus
repo={config.githubRepo as GiscusProps["repo"]} repo={env.NEXT_PUBLIC_GITHUB_REPO as GiscusProps["repo"]}
repoId={env.NEXT_PUBLIC_GISCUS_REPO_ID} repoId={env.NEXT_PUBLIC_GISCUS_REPO_ID}
term={title} term={title}
category="Comments" category="Comments"

View File

@ -1,8 +1,8 @@
import { env } from "../../lib/env";
import clsx from "clsx"; import clsx from "clsx";
import { HeartIcon } from "lucide-react"; import { HeartIcon } from "lucide-react";
import Link from "../Link"; import Link from "../Link";
import * as config from "../../lib/config"; import * as config from "../../lib/config";
import { RELEASE_TIMESTAMP } from "../../lib/config/constants";
import type { ComponentPropsWithoutRef } from "react"; import type { ComponentPropsWithoutRef } from "react";
import styles from "./Footer.module.css"; import styles from "./Footer.module.css";
@ -22,7 +22,7 @@ const Footer = ({ className, ...rest }: FooterProps) => {
<Link href="/previously" title="Previously on..." plain className={styles.link}> <Link href="/previously" title="Previously on..." plain className={styles.link}>
{config.copyrightYearStart} {config.copyrightYearStart}
</Link>{" "} </Link>{" "}
{new Date(RELEASE_TIMESTAMP).getUTCFullYear()}. {new Date().getUTCFullYear()}.
</div> </div>
<div> <div>
@ -53,7 +53,7 @@ const Footer = ({ className, ...rest }: FooterProps) => {
</Link> </Link>
.{" "} .{" "}
<Link <Link
href={`https://github.com/${config.githubRepo}`} href={`https://github.com/${env.NEXT_PUBLIC_GITHUB_REPO}`}
title="View Source on GitHub" title="View Source on GitHub"
plain plain
className={clsx(styles.link, styles.underline)} className={clsx(styles.link, styles.underline)}

View File

@ -1,8 +1,8 @@
import { env } from "../../lib/env";
import { format, formatISO } from "date-fns"; import { format, formatISO } from "date-fns";
import { enUS } from "date-fns/locale"; import { enUS } from "date-fns/locale";
import { tz } from "@date-fns/tz"; import { tz } from "@date-fns/tz";
import { utc } from "@date-fns/utc"; import { utc } from "@date-fns/utc";
import { SITE_TZ } from "../../lib/config/constants";
import type { ComponentPropsWithoutRef } from "react"; import type { ComponentPropsWithoutRef } from "react";
export type TimeProps = ComponentPropsWithoutRef<"time"> & { export type TimeProps = ComponentPropsWithoutRef<"time"> & {
@ -14,10 +14,10 @@ const Time = ({ date, format: formatStr = "PPpp", ...rest }: TimeProps) => {
return ( return (
<time <time
dateTime={formatISO(date, { in: utc })} dateTime={formatISO(date, { in: utc })}
title={format(date, "MMM d, y, h:mm a O", { in: tz(SITE_TZ), locale: enUS })} title={format(date, "MMM d, y, h:mm a O", { in: tz(env.NEXT_PUBLIC_SITE_TZ), locale: enUS })}
{...rest} {...rest}
> >
{format(date, formatStr, { in: tz(SITE_TZ), locale: enUS })} {format(date, formatStr, { in: tz(env.NEXT_PUBLIC_SITE_TZ), locale: enUS })}
</time> </time>
); );
}; };

View File

@ -1,16 +1,3 @@
/**
* Locale code to define the site's language in ISO-639 format. Defaults to `en-US`.
* @see https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes#Table
*/
export const SITE_LOCALE = "en-US";
/**
* Consistent timezone for the site. Doesn't really matter what it is, as long as it's the same everywhere to avoid
* hydration complaints.
* @see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
*/
export const SITE_TZ = "America/New_York";
/** Path to directory with .mdx files, relative to project root. */ /** Path to directory with .mdx files, relative to project root. */
export const POSTS_DIR = "notes"; export const POSTS_DIR = "notes";
@ -22,9 +9,3 @@ export const AVATAR_PATH = "app/avatar.jpg";
/** Maximum width of content wrapper (e.g. for images) in pixels. */ /** Maximum width of content wrapper (e.g. for images) in pixels. */
export const MAX_WIDTH = 865; export const MAX_WIDTH = 865;
/** Chooses the most appropriate URL for the current deployment. Defined in [`next.config.ts`](../../next.config.ts). */
export const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL!;
/** Freezes time at build. Defined in [`next.config.ts`](../../next.config.ts). */
export const RELEASE_TIMESTAMP = process.env.NEXT_PUBLIC_RELEASE_TIMESTAMP!;

View File

@ -7,7 +7,6 @@ export const license = "Creative Commons Attribution 4.0 International";
export const licenseAbbr = "CC-BY-4.0"; export const licenseAbbr = "CC-BY-4.0";
export const licenseUrl = "https://creativecommons.org/licenses/by/4.0/"; export const licenseUrl = "https://creativecommons.org/licenses/by/4.0/";
export const copyrightYearStart = 2001; export const copyrightYearStart = 2001;
export const githubRepo = "jakejarvis/jarv.is";
// Me info // Me info
export const authorName = "Jake Jarvis"; export const authorName = "Jake Jarvis";

View File

@ -64,6 +64,13 @@ export const env = createEnv({
TURNSTILE_SECRET_KEY: v.optional(v.string(), "1x0000000000000000000000000000000AA"), TURNSTILE_SECRET_KEY: v.optional(v.string(), "1x0000000000000000000000000000000AA"),
}, },
client: { client: {
/**
* Optional. Overrides the most appropriate default URL for the current deployment.
*
* @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata#default-value
*/
NEXT_PUBLIC_BASE_URL: v.optional(v.pipe(v.string(), v.url())),
/** /**
* Optional. Enables comments on blog posts via GitHub discussions. * Optional. Enables comments on blog posts via GitHub discussions.
* *
@ -77,6 +84,11 @@ export const env = createEnv({
*/ */
NEXT_PUBLIC_GISCUS_REPO_ID: v.optional(v.string()), NEXT_PUBLIC_GISCUS_REPO_ID: v.optional(v.string()),
/**
* Required. GitHub repository for the site in the format of `{username}/{repo}`.
*/
NEXT_PUBLIC_GITHUB_REPO: v.string(),
/** /**
* Optional. Sets an `Onion-Location` header in responses to advertise a URL for the same page but hosted on a * Optional. Sets an `Onion-Location` header in responses to advertise a URL for the same page but hosted on a
* hidden service on the Tor network. Browsers like Brave and Tor Browser will automatically pick this up and offer * hidden service on the Tor network. Browsers like Brave and Tor Browser will automatically pick this up and offer
@ -86,6 +98,21 @@ export const env = createEnv({
*/ */
NEXT_PUBLIC_ONION_DOMAIN: v.optional(v.pipe(v.string(), v.endsWith(".onion"))), NEXT_PUBLIC_ONION_DOMAIN: v.optional(v.pipe(v.string(), v.endsWith(".onion"))),
/**
* Optional. Locale code to define the site's language in ISO-639 format. Defaults to `en-US`.
*
* @see https://en.wikipedia.org/wiki/List_of_ISO_639_language_codes#Table
*/
NEXT_PUBLIC_SITE_LOCALE: v.optional(v.string(), "en-US"),
/**
* Optional. Consistent timezone for the site. Doesn't really matter what it is, as long as it's the same everywhere
* to avoid hydration complaints.
*
* @see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
*/
NEXT_PUBLIC_SITE_TZ: v.optional(v.string(), "America/New_York"),
/** /**
* Required. Site key must be prefixed with NEXT_PUBLIC_ since it is used to embed the captcha widget. Falls back to * Required. Site key must be prefixed with NEXT_PUBLIC_ since it is used to embed the captcha widget. Falls back to
* testing keys if not set or in dev environment. * testing keys if not set or in dev environment.
@ -95,9 +122,36 @@ export const env = createEnv({
NEXT_PUBLIC_TURNSTILE_SITE_KEY: v.optional(v.string(), "XXXX.DUMMY.TOKEN.XXXX"), NEXT_PUBLIC_TURNSTILE_SITE_KEY: v.optional(v.string(), "XXXX.DUMMY.TOKEN.XXXX"),
}, },
experimental__runtimeEnv: { experimental__runtimeEnv: {
NEXT_PUBLIC_BASE_URL:
process.env.NEXT_PUBLIC_BASE_URL ||
// Vercel: https://vercel.com/docs/environment-variables/system-environment-variables
(process.env.VERCEL
? process.env.VERCEL_ENV === "production" && process.env.VERCEL_PROJECT_PRODUCTION_URL
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
: process.env.VERCEL_ENV === "preview" && process.env.VERCEL_BRANCH_URL
? `https://${process.env.VERCEL_BRANCH_URL}`
: process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: undefined
: undefined) ||
// Netlify: https://docs.netlify.com/configure-builds/environment-variables/#read-only-variables
(process.env.NETLIFY
? process.env.CONTEXT === "production" && process.env.URL
? `${process.env.URL}`
: process.env.DEPLOY_PRIME_URL
? `${process.env.DEPLOY_PRIME_URL}`
: process.env.DEPLOY_URL
? `${process.env.DEPLOY_URL}`
: undefined
: undefined) ||
// next dev
`http://localhost:${process.env.PORT || 3000}`,
NEXT_PUBLIC_GISCUS_CATEGORY_ID: process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID, NEXT_PUBLIC_GISCUS_CATEGORY_ID: process.env.NEXT_PUBLIC_GISCUS_CATEGORY_ID,
NEXT_PUBLIC_GISCUS_REPO_ID: process.env.NEXT_PUBLIC_GISCUS_REPO_ID, NEXT_PUBLIC_GISCUS_REPO_ID: process.env.NEXT_PUBLIC_GISCUS_REPO_ID,
NEXT_PUBLIC_GITHUB_REPO: process.env.NEXT_PUBLIC_GITHUB_REPO,
NEXT_PUBLIC_ONION_DOMAIN: process.env.NEXT_PUBLIC_ONION_DOMAIN, NEXT_PUBLIC_ONION_DOMAIN: process.env.NEXT_PUBLIC_ONION_DOMAIN,
NEXT_PUBLIC_SITE_LOCALE: process.env.NEXT_PUBLIC_SITE_LOCALE,
NEXT_PUBLIC_SITE_TZ: process.env.NEXT_PUBLIC_SITE_TZ,
NEXT_PUBLIC_TURNSTILE_SITE_KEY: process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY, NEXT_PUBLIC_TURNSTILE_SITE_KEY: process.env.NEXT_PUBLIC_TURNSTILE_SITE_KEY,
}, },
emptyStringAsUndefined: true, emptyStringAsUndefined: true,

View File

@ -1,7 +1,7 @@
import { env } from "../../lib/env";
import { Feed } from "feed"; import { Feed } from "feed";
import { getFrontMatter, getContent } from "./posts"; import { getFrontMatter, getContent } from "./posts";
import * as config from "../config"; import * as config from "../config";
import { BASE_URL, RELEASE_TIMESTAMP } from "../config/constants";
import type { Item as FeedItem } from "feed"; import type { Item as FeedItem } from "feed";
import ogImage from "../../app/opengraph-image.jpg"; import ogImage from "../../app/opengraph-image.jpg";
@ -12,20 +12,20 @@ import ogImage from "../../app/opengraph-image.jpg";
*/ */
export const buildFeed = async (): Promise<Feed> => { export const buildFeed = async (): Promise<Feed> => {
const feed = new Feed({ const feed = new Feed({
id: `${BASE_URL}`, id: `${env.NEXT_PUBLIC_BASE_URL}`,
link: `${BASE_URL}`, link: `${env.NEXT_PUBLIC_BASE_URL}`,
title: config.siteName, title: config.siteName,
description: config.description, description: config.description,
copyright: config.licenseUrl, copyright: config.licenseUrl,
updated: new Date(RELEASE_TIMESTAMP), updated: new Date(),
image: `${BASE_URL}${ogImage.src}`, image: `${env.NEXT_PUBLIC_BASE_URL}${ogImage.src}`,
feedLinks: { feedLinks: {
rss: `${BASE_URL}/feed.xml`, rss: `${env.NEXT_PUBLIC_BASE_URL}/feed.xml`,
atom: `${BASE_URL}/feed.atom`, atom: `${env.NEXT_PUBLIC_BASE_URL}/feed.atom`,
}, },
author: { author: {
name: config.authorName, name: config.authorName,
link: BASE_URL, link: env.NEXT_PUBLIC_BASE_URL,
email: config.authorEmail, email: config.authorEmail,
}, },
}); });
@ -41,7 +41,7 @@ export const buildFeed = async (): Promise<Feed> => {
author: [ author: [
{ {
name: config.authorName, name: config.authorName,
link: `${BASE_URL}`, link: `${env.NEXT_PUBLIC_BASE_URL}`,
}, },
], ],
date: new Date(post.date), date: new Date(post.date),

View File

@ -1,9 +1,9 @@
import { env } from "../env";
import * as config from "../config"; import * as config from "../config";
import { BASE_URL, SITE_LOCALE } from "../config/constants";
import type { Metadata } from "next"; import type { Metadata } from "next";
export const defaultMetadata: Metadata = { export const defaultMetadata: Metadata = {
metadataBase: new URL(BASE_URL), metadataBase: env.NEXT_PUBLIC_BASE_URL ? new URL(env.NEXT_PUBLIC_BASE_URL) : undefined,
title: { title: {
template: `%s ${config.siteName}`, template: `%s ${config.siteName}`,
default: `${config.siteName} ${config.tagline}`, default: `${config.siteName} ${config.tagline}`,
@ -16,7 +16,7 @@ export const defaultMetadata: Metadata = {
default: `${config.siteName} ${config.tagline}`, default: `${config.siteName} ${config.tagline}`,
}, },
url: "/", url: "/",
locale: SITE_LOCALE?.replace("-", "_"), locale: env.NEXT_PUBLIC_SITE_LOCALE.replace("-", "_"),
type: "website", type: "website",
}, },
twitter: { twitter: {

View File

@ -1,10 +1,11 @@
import { env } from "../../lib/env";
import path from "path"; import path from "path";
import fs from "fs/promises"; import fs from "fs/promises";
import glob from "fast-glob"; import glob from "fast-glob";
import { unified } from "unified"; import { unified } from "unified";
import { remarkHtml, remarkParse, remarkSmartypants, remarkFrontmatter } from "./remark-rehype-plugins"; import { remarkHtml, remarkParse, remarkSmartypants, remarkFrontmatter } from "./remark-rehype-plugins";
import { decode } from "html-entities"; import { decode } from "html-entities";
import { BASE_URL, POSTS_DIR } from "../config/constants"; import { POSTS_DIR } from "../config/constants";
export type FrontMatter = { export type FrontMatter = {
slug: string; slug: string;
@ -77,7 +78,7 @@ Promise<any> => {
slug, slug,
// validate/normalize the date string provided from front matter // validate/normalize the date string provided from front matter
date: new Date(frontmatter.date).toISOString(), date: new Date(frontmatter.date).toISOString(),
permalink: `${BASE_URL}/${POSTS_DIR}/${slug}`, permalink: `${env.NEXT_PUBLIC_BASE_URL}/${POSTS_DIR}/${slug}`,
} as FrontMatter; } as FrontMatter;
} catch (error) { } catch (error) {
console.error(`Failed to load front matter for post with slug "${slug}":`, error); console.error(`Failed to load front matter for post with slug "${slug}":`, error);

View File

@ -12,21 +12,6 @@ import "./lib/env";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
reactStrictMode: true, reactStrictMode: true,
productionBrowserSourceMaps: true, productionBrowserSourceMaps: true,
env: {
// same logic as metadataBase: https://nextjs.org/docs/app/api-reference/functions/generate-metadata#default-value
NEXT_PUBLIC_BASE_URL:
process.env.VERCEL_ENV === "production" && process.env.VERCEL_PROJECT_PRODUCTION_URL
? `https://${process.env.VERCEL_PROJECT_PRODUCTION_URL}`
: process.env.VERCEL_ENV === "preview" && process.env.VERCEL_BRANCH_URL
? `https://${process.env.VERCEL_BRANCH_URL}`
: process.env.VERCEL_URL
? `https://${process.env.VERCEL_URL}`
: `http://localhost:${process.env.PORT || 3000}`,
// freeze timestamp at build time for when server-side pages need a "last updated" date. calling Date.now() from
// pages using getServerSideProps will return the current(ish) time instead, which is usually not what we want.
NEXT_PUBLIC_RELEASE_TIMESTAMP: new Date().toISOString(),
},
pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"], pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"],
outputFileTracingIncludes: { outputFileTracingIncludes: {
"/notes/[slug]/opengraph-image": [ "/notes/[slug]/opengraph-image": [