diff --git a/app/layout.tsx b/app/layout.tsx index 3cc098cc..b7e82252 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -7,7 +7,7 @@ import Footer from "../components/Footer"; import { SkipNavLink, SkipNavTarget } from "../components/SkipNav"; import { defaultMetadata } from "../lib/helpers/metadata"; import * as config from "../lib/config"; -import { BASE_URL, MAX_WIDTH } from "../lib/config/constants"; +import { BASE_URL, MAX_WIDTH, SITE_LOCALE } from "../lib/config/constants"; import type { Metadata } from "next"; import type { Person, WebSite } from "schema-dts"; @@ -21,7 +21,7 @@ export const metadata: Metadata = defaultMetadata; const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => { return ( - + @@ -55,8 +55,8 @@ const RootLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => { name: config.siteName, url: BASE_URL, author: config.authorName, - description: config.longDescription, - inLanguage: config.siteLocale, + description: config.description, + inLanguage: SITE_LOCALE, license: config.licenseUrl, }} /> diff --git a/app/manifest.ts b/app/manifest.ts index e4071664..4f8e1eba 100644 --- a/app/manifest.ts +++ b/app/manifest.ts @@ -1,4 +1,5 @@ import * as config from "../lib/config"; +import { SITE_LOCALE } from "../lib/config/constants"; import type { MetadataRoute } from "next"; const manifest = (): MetadataRoute.Manifest => { @@ -6,8 +7,8 @@ const manifest = (): MetadataRoute.Manifest => { name: config.siteName, // eslint-disable-next-line camelcase short_name: config.siteName, - description: config.longDescription, - lang: config.siteLocale, + description: config.description, + lang: SITE_LOCALE, icons: [ { src: "/icon.png", diff --git a/app/notes/[slug]/counter.tsx b/app/notes/[slug]/counter.tsx index 9641084e..42c057aa 100644 --- a/app/notes/[slug]/counter.tsx +++ b/app/notes/[slug]/counter.tsx @@ -1,7 +1,7 @@ import { connection } from "next/server"; import CountUp from "../../../components/CountUp"; import redis from "../../../lib/redis"; -import { siteLocale } from "../../../lib/config"; +import { SITE_LOCALE } from "../../../lib/config/constants"; const HitCounter = async ({ slug }: { slug: string }) => { await connection(); @@ -16,7 +16,7 @@ const HitCounter = async ({ slug }: { slug: string }) => { // we have data! return ( - + ); diff --git a/app/notes/[slug]/page.tsx b/app/notes/[slug]/page.tsx index 9ac929c3..48c711e2 100644 --- a/app/notes/[slug]/page.tsx +++ b/app/notes/[slug]/page.tsx @@ -10,7 +10,7 @@ import HitCounter from "./counter"; import { getSlugs, getFrontMatter } from "../../../lib/helpers/posts"; import { addMetadata } from "../../../lib/helpers/metadata"; import * as config from "../../../lib/config"; -import { BASE_URL, POSTS_DIR } from "../../../lib/config/constants"; +import { BASE_URL, POSTS_DIR, SITE_LOCALE } from "../../../lib/config/constants"; import { size as ogImageSize } from "./opengraph-image"; import type { Metadata } from "next"; import type { BlogPosting } from "schema-dts"; @@ -79,7 +79,7 @@ const Page = async ({ params }: { params: Promise<{ slug: string }> }) => { keywords: frontmatter!.tags?.join(", "), datePublished: frontmatter!.date, dateModified: frontmatter!.date, - inLanguage: config.siteLocale, + inLanguage: SITE_LOCALE, license: config.licenseUrl, author: { // defined in app/layout.tsx diff --git a/app/projects/page.tsx b/app/projects/page.tsx index d881a011..015f61e5 100644 --- a/app/projects/page.tsx +++ b/app/projects/page.tsx @@ -7,6 +7,7 @@ import Link from "../../components/Link"; import RelativeTime from "../../components/RelativeTime"; import { addMetadata } from "../../lib/helpers/metadata"; import * as config from "../../lib/config"; +import { SITE_LOCALE } from "../../lib/config/constants"; import type { User } from "@octokit/graphql-schema"; import styles from "./page.module.css"; @@ -120,12 +121,12 @@ const Page = async () => {
- {Intl.NumberFormat(config.siteLocale || "en-US").format(repo!.stargazerCount)} + {Intl.NumberFormat(SITE_LOCALE || "en-US").format(repo!.stargazerCount)}
)} @@ -134,12 +135,12 @@ const Page = async () => {
- {Intl.NumberFormat(config.siteLocale || "en-US").format(repo!.forkCount)} + {Intl.NumberFormat(SITE_LOCALE || "en-US").format(repo!.forkCount)}
)} diff --git a/components/Time/Time.tsx b/components/Time/Time.tsx index 7323acdf..70b33281 100644 --- a/components/Time/Time.tsx +++ b/components/Time/Time.tsx @@ -2,7 +2,7 @@ import { format, formatISO } from "date-fns"; import { enUS } from "date-fns/locale"; import { tz } from "@date-fns/tz"; import { utc } from "@date-fns/utc"; -import * as config from "../../lib/config"; +import { SITE_TZ } from "../../lib/config/constants"; import type { ComponentPropsWithoutRef } from "react"; export type TimeProps = ComponentPropsWithoutRef<"time"> & { @@ -14,10 +14,10 @@ const Time = ({ date, format: formatStr = "PPpp", ...rest }: TimeProps) => { return ( ); }; diff --git a/lib/config/constants.ts b/lib/config/constants.ts index 65793fb1..cb02c029 100644 --- a/lib/config/constants.ts +++ b/lib/config/constants.ts @@ -1,13 +1,30 @@ -// path to directory with .mdx files, relative to project root +/** + * 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. */ export const POSTS_DIR = "notes"; -// path to an image used in various places to represent the site, relative to project root -// IMPORTANT: must be included in next.config.ts under "outputFileTracingIncludes" +/** + * Path to an image used in various places to represent the site, relative to project root. This path must be included + * in [next.config.ts](../../next.config.ts) under `outputFileTracingIncludes`. + */ 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; -// defined in next.config.ts +/** 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!; diff --git a/lib/config/index.ts b/lib/config/index.ts index eb4b659b..e48bf4ea 100644 --- a/lib/config/index.ts +++ b/lib/config/index.ts @@ -1,9 +1,7 @@ // Site info export const siteName = "Jake Jarvis"; -export const siteLocale = "en-US"; -export const timeZone = "America/New_York"; // https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List -export const shortDescription = "Frontend Web Developer in Boston, MA"; -export const longDescription = +export const tagline = "Frontend Web Developer in Boston, MA"; +export const description = "Hi there! I'm a frontend web developer based in Boston, Massachusetts specializing in TypeScript, React, Next.js, and other JavaScript frameworks."; export const license = "Creative Commons Attribution 4.0 International"; export const licenseAbbr = "CC-BY-4.0"; diff --git a/lib/env.ts b/lib/env.ts index 38accf77..af205b68 100644 --- a/lib/env.ts +++ b/lib/env.ts @@ -11,15 +11,15 @@ export const env = createEnv({ ], server: { /** - * Required. GitHub API token used for /projects grid. Only needs the `public_repo` scope since we don't need/want - * to change anything, obviously. + * Required. GitHub API token used for [/projects](../app/projects/page.tsx) grid. Only needs the `public_repo` + * scope since we don't need/want to change anything, obviously. * * @see https://github.com/settings/tokens/new?scopes=public_repo */ GITHUB_TOKEN: v.optional(v.pipe(v.string(), v.startsWith("ghp_"))), /** - * Required. Redis storage credentials for hit counter's server component (app/notes/[slug]/counter.tsx) and API + * Required. Redis storage credentials for hit counter's [server component](../app/notes/[slug]/counter.tsx) and API * endpoint. Currently set automatically by Vercel's Upstash integration. * * @see https://upstash.com/docs/redis/sdks/ts/getstarted @@ -27,7 +27,7 @@ export const env = createEnv({ */ KV_REST_API_TOKEN: v.string(), /** - * Required. Redis storage credentials for hit counter's server component (app/notes/[slug]/counter.tsx) and API + * Required. Redis storage credentials for hit counter's [server component](../app/notes/[slug]/counter.tsx) and API * endpoint. Currently set automatically by Vercel's Upstash integration. * * @see https://upstash.com/docs/redis/sdks/ts/getstarted @@ -36,7 +36,7 @@ export const env = createEnv({ KV_REST_API_URL: v.pipe(v.string(), v.url(), v.startsWith("https://"), v.endsWith(".upstash.io")), /** - * Required. Uses Resend API to send contact form submissions via a server action (see app/contact/actions.ts). May + * Required. Uses Resend API to send contact form submissions via a [server action](../app/contact/action.ts). May * be set automatically by Vercel's Resend integration. * * @see https://resend.com/api-keys diff --git a/lib/helpers/build-feed.ts b/lib/helpers/build-feed.ts index 94718402..033273a7 100644 --- a/lib/helpers/build-feed.ts +++ b/lib/helpers/build-feed.ts @@ -15,7 +15,7 @@ export const buildFeed = async (): Promise => { id: `${BASE_URL}`, link: `${BASE_URL}`, title: config.siteName, - description: config.longDescription, + description: config.description, copyright: config.licenseUrl, updated: new Date(RELEASE_TIMESTAMP), image: `${BASE_URL}${ogImage.src}`, diff --git a/lib/helpers/metadata.ts b/lib/helpers/metadata.ts index f818c15c..43033d2d 100644 --- a/lib/helpers/metadata.ts +++ b/lib/helpers/metadata.ts @@ -1,22 +1,22 @@ import * as config from "../config"; -import { BASE_URL } from "../config/constants"; +import { BASE_URL, SITE_LOCALE } from "../config/constants"; import type { Metadata } from "next"; export const defaultMetadata: Metadata = { metadataBase: new URL(BASE_URL), title: { template: `%s – ${config.siteName}`, - default: `${config.siteName} – ${config.shortDescription}`, + default: `${config.siteName} – ${config.tagline}`, }, - description: config.longDescription, + description: config.description, openGraph: { siteName: config.siteName, title: { template: "%s", - default: `${config.siteName} – ${config.shortDescription}`, + default: `${config.siteName} – ${config.tagline}`, }, url: "/", - locale: config.siteLocale?.replace("-", "_"), + locale: SITE_LOCALE?.replace("-", "_"), type: "website", }, twitter: {