1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-06-30 11:36:38 -04:00

attempt to make edge functions a tad bit lighter

This commit is contained in:
2023-07-06 10:37:51 -04:00
parent 2f44d8d227
commit b13c8259b3
17 changed files with 121 additions and 146 deletions

View File

@ -1,7 +1,6 @@
import NextLink from "next/link"; import NextLink from "next/link";
import objStr from "obj-str"; import objStr from "obj-str";
import { styled, theme, stitchesConfig } from "../../lib/styles/stitches.config"; import { styled, theme, stitchesConfig } from "../../lib/styles/stitches.config";
import { baseUrl } from "../../lib/config";
import type { ComponentProps } from "react"; import type { ComponentProps } from "react";
const StyledLink = styled(NextLink, { const StyledLink = styled(NextLink, {
@ -43,7 +42,8 @@ const Link = ({ href, rel, target, prefetch = false, underline = true, openInNew
// This component auto-detects whether or not this link should open in the same window (the default for internal // This component auto-detects whether or not this link should open in the same window (the default for internal
// links) or a new tab (the default for external links). Defaults can be overridden with `openInNewTab={true}`. // links) or a new tab (the default for external links). Defaults can be overridden with `openInNewTab={true}`.
const isExternal = const isExternal =
typeof href === "string" && !(href.startsWith("/") || href.startsWith("#") || href.startsWith(baseUrl)); typeof href === "string" &&
!(href.startsWith("/") || href.startsWith("#") || (process.env.BASE_URL && href.startsWith(process.env.BASE_URL)));
if (openInNewTab || isExternal) { if (openInNewTab || isExternal) {
return ( return (

View File

@ -1,34 +1,34 @@
import { useMemo } from "react"; import { memo } from "react";
import { minify } from "uglify-js"; import { minify } from "uglify-js";
import { clientScript } from "./client"; import type { MinifyOutput } from "uglify-js";
import { IS_DEV_SERVER } from "../../lib/config/constants";
export type ThemeScriptProps = JSX.IntrinsicElements["script"] & { import { clientScript } from "./client.js";
export type ThemeScriptProps = {
themeClassNames: { themeClassNames: {
[themeName: string]: string; [themeName: string]: string;
}; };
themeStorageKey: string; themeStorageKey: string;
}; };
const ThemeScript = ({ key, themeClassNames, themeStorageKey, ...rest }: ThemeScriptProps) => { // eslint-disable-next-line react/display-name
const minified = useMemo(() => { const ThemeScript = memo<ThemeScriptProps>(({ themeClassNames, themeStorageKey }) => {
// since the client function will end up being injected as a plain dumb string, we need to set dynamic values here: const minified = (() => {
const functionString = String(clientScript) // since the client function will end up being injected as a static hard-coded string, we need to determine all of
.replace('"__MEDIA_QUERY__"', `"(prefers-color-scheme: dark)"`) // the dynamic values within it *before* generating the final script.
.replace('"__STORAGE_KEY__"', `"${themeStorageKey}"`) const source = String(clientScript)
.replace('"__CLASS_NAMES__"', JSON.stringify(themeClassNames)); .replace("__MEDIA_QUERY__", "(prefers-color-scheme: dark)")
.replace("__STORAGE_KEY__", themeStorageKey)
.replace("__CLASS_NAMES__", Object.values(themeClassNames).join('","'));
// turn the raw function into an iife // turn the raw function into an iife
const unminified = `(${functionString})()`; const unminified = `(${source})()`;
// skip minification if running locally to save a few ms // minify the final code. this approach is a bit janky but is ONLY used at build time, so there's essentially no
if (IS_DEV_SERVER) { // risk of breaking the entire site and/or accidentally bundling uglify-js clientside (bad).
return unminified; let minified: MinifyOutput | undefined;
} try {
minified = minify(unminified, {
// minify the final code, a bit hacky but this is ONLY done at build-time, so uglify-js is never bundled or sent to
// the browser to execute.
const minified = minify(unminified, {
toplevel: true, toplevel: true,
compress: { compress: {
negate_iife: false, negate_iife: false,
@ -37,15 +37,21 @@ const ThemeScript = ({ key, themeClassNames, themeStorageKey, ...rest }: ThemeSc
bare_returns: true, bare_returns: true,
}, },
}); });
} catch (error) {
// fail somewhat silenty by returning the unminified version // fail somewhat silenty by returning the unminified version
console.warn("Failed to minify inline theme script:", error);
return unminified;
}
// same as the catch block above, but in some cases (not really sure when), minify() doesn't throw an actual error
// and instead just returns undefined and an "error" string, so we need to check for both.
if (!minified || minified.error) { if (!minified || minified.error) {
console.warn("Failed to minify inline theme script:", minified.error); console.warn("Failed to minify inline theme script. uglify-js output:", minified.error);
return unminified; return unminified;
} }
return minified.code; return minified.code;
}, [themeClassNames, themeStorageKey]); })();
// the script tag injected manually into `<head>` in _document.tsx to prevent FARTing: // the script tag injected manually into `<head>` in _document.tsx to prevent FARTing:
// https://css-tricks.com/flash-of-inaccurate-color-theme-fart/ // https://css-tricks.com/flash-of-inaccurate-color-theme-fart/
@ -54,14 +60,13 @@ const ThemeScript = ({ key, themeClassNames, themeStorageKey, ...rest }: ThemeSc
// TODO: using next/script *might* be possible after https://github.com/vercel/next.js/pull/36364 is merged. // TODO: using next/script *might* be possible after https://github.com/vercel/next.js/pull/36364 is merged.
return ( return (
<script <script
key={key} // separate on purpose! id="restore-theme"
{...rest}
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
// make it an IIFE: // make it an IIFE:
__html: `(function(){${minified}})()`, __html: `(function(){${minified}})()`,
}} }}
/> />
); );
}; });
export default ThemeScript; export default ThemeScript;

View File

@ -1,38 +1,25 @@
// @ts-check // @ts-check
/* eslint-disable no-var, no-empty */
// this function is converted to a string verbatim, substitutions are made to insert dynamic values, minified, and then // this function is converted to a string verbatim, substitutions are made to insert dynamic values, minified, and then
// finally exported as an inline `<script>` tag in ThemeScript.tsx for _document.tsx to use. // finally exported as an inline `<script>` tag in ThemeScript.tsx for _document.tsx to use.
export const clientScript = () => { export const clientScript = () => {
// `try/catch` in case I messed something up here bigly... (will default to light theme) // `try/catch` in case I messed something up here bigly... (will default to light theme)
try { try {
// help minifier minify
var light = "light";
var dark = "dark";
var newTheme;
// the list of <html>'s current class(es)... // the list of <html>'s current class(es)...
// eslint-disable-next-line prefer-destructuring const { classList } = document.documentElement;
var classList = document.documentElement.classList; // map of themes -> classnames ([0]=light, [1]=dark)
// map of theme -> classname const classNames = ["__CLASS_NAMES__"];
var classNames = "__CLASS_NAMES__";
// user's saved preference // user's saved preference
var pref = window.localStorage.getItem("__STORAGE_KEY__"); const pref = window.localStorage.getItem("__STORAGE_KEY__");
if (pref && (pref === light || pref === dark)) { // restore the local storage preference if it's set, otherwise test CSS media query for browser dark mode preference
// simply restore the local storage preference
newTheme = pref;
} else {
// test CSS media query for system dark mode preference
// https://stackoverflow.com/a/57795495/1438024 // https://stackoverflow.com/a/57795495/1438024
newTheme = window.matchMedia("__MEDIA_QUERY__").matches ? dark : light; const newTheme = (pref && pref === "dark") || window.matchMedia("__MEDIA_QUERY__").matches ? 1 : 0;
}
// remove both `classNames` to start fresh... // remove both `classNames` to start fresh...
// @ts-ignore classList.remove(classNames[0], classNames[1]);
classList.remove(classNames[light], classNames[dark]);
// ...and then FINALLY set the root class // ...and then FINALLY set the root class
// @ts-ignore
classList.add(classNames[newTheme]); classList.add(classNames[newTheme]);
} catch (error) {} } catch (error) {} // eslint-disable-line no-empty
}; };

View File

@ -1,4 +1,4 @@
import { createContext, useCallback, useEffect, useState } from "react"; import { createContext, useCallback, useEffect, useMemo, useState } from "react";
import useLocalStorage from "../hooks/useLocalStorage"; import useLocalStorage from "../hooks/useLocalStorage";
import useMedia from "../hooks/useMedia"; import useMedia from "../hooks/useMedia";
import { themeStorageKey } from "../lib/styles/stitches.config"; import { themeStorageKey } from "../lib/styles/stitches.config";
@ -77,22 +77,18 @@ export const ThemeProvider = ({
document.documentElement.style?.setProperty("color-scheme", colorScheme); document.documentElement.style?.setProperty("color-scheme", colorScheme);
}, [preferredTheme, systemTheme]); }, [preferredTheme, systemTheme]);
return ( const providerValues = useMemo(
<ThemeContext.Provider () => ({
value={{
activeTheme: preferredTheme && themeNames.includes(preferredTheme) ? preferredTheme : systemTheme, activeTheme: preferredTheme && themeNames.includes(preferredTheme) ? preferredTheme : systemTheme,
setTheme: useCallback( setTheme: (theme: string) => {
(theme: string) => {
// force save to local storage // force save to local storage
changeTheme(theme, true); changeTheme(theme, true);
}, },
[changeTheme] }),
), [changeTheme, preferredTheme, systemTheme, themeNames]
}}
>
{children}
</ThemeContext.Provider>
); );
return <ThemeContext.Provider value={providerValues}>{children}</ThemeContext.Provider>;
}; };
// debugging help pls // debugging help pls

View File

@ -1,13 +0,0 @@
// Next.js constants (not needed in frontend)
// directory containing .mdx files relative to project root
export const NOTES_DIR = "notes";
// normalize the timestamp saved when building/deploying (see next.config.js) and fall back to right now
export const RELEASE_DATE = new Date(process.env.RELEASE_DATE || Date.now()).toISOString();
// detect if running locally via `next dev` (phase is checked in next.config.js)
export const IS_DEV_SERVER = process.env.IS_DEV_SERVER === "true";
// attempt to normalize the various environment flags
export const BUILD_ENV = process.env.VERCEL_ENV || process.env.NEXT_PUBLIC_VERCEL_ENV || process.env.NODE_ENV;

View File

@ -6,7 +6,6 @@ module.exports = {
siteDomain: "jarv.is", siteDomain: "jarv.is",
siteLocale: "en-US", siteLocale: "en-US",
timeZone: "America/New_York", // https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List timeZone: "America/New_York", // https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List
baseUrl: process.env.BASE_URL || "", // see next.config.js
onionDomain: "http://jarvis2i2vp4j4tbxjogsnqdemnte5xhzyi7hziiyzxwge3hzmh57zad.onion", onionDomain: "http://jarvis2i2vp4j4tbxjogsnqdemnte5xhzyi7hziiyzxwge3hzmh57zad.onion",
shortDescription: "Front-End Web Developer in Boston, MA", shortDescription: "Front-End Web Developer in Boston, MA",
longDescription: longDescription:

View File

@ -17,7 +17,7 @@ export const defaultSeo: DefaultSeoProps = {
type: "website", type: "website",
images: [ images: [
{ {
url: `${config.baseUrl}${meJpg.src}`, url: `${process.env.BASE_URL}${meJpg.src}`,
alt: `${config.siteName} ${config.shortDescription}`, alt: `${config.siteName} ${config.shortDescription}`,
}, },
], ],
@ -103,9 +103,9 @@ export const defaultSeo: DefaultSeoProps = {
export const socialProfileJsonLd: SocialProfileJsonLdProps = { export const socialProfileJsonLd: SocialProfileJsonLdProps = {
type: "Person", type: "Person",
name: config.authorName, name: config.authorName,
url: `${config.baseUrl}/`, url: `${process.env.BASE_URL}/`,
sameAs: [ sameAs: [
`${config.baseUrl}/`, `${process.env.BASE_URL}/`,
`https://github.com/${config.authorSocial?.github}`, `https://github.com/${config.authorSocial?.github}`,
`https://keybase.io/${config.authorSocial?.keybase}`, `https://keybase.io/${config.authorSocial?.keybase}`,
`https://twitter.com/${config.authorSocial?.twitter}`, `https://twitter.com/${config.authorSocial?.twitter}`,
@ -122,5 +122,5 @@ export const socialProfileJsonLd: SocialProfileJsonLdProps = {
export const articleJsonLd: Pick<ArticleJsonLdProps, "authorName" | "publisherName" | "publisherLogo"> = { export const articleJsonLd: Pick<ArticleJsonLdProps, "authorName" | "publisherName" | "publisherLogo"> = {
authorName: [config.authorName], authorName: [config.authorName],
publisherName: config.siteName, publisherName: config.siteName,
publisherLogo: `${config.baseUrl}${meJpg.src}`, publisherLogo: `${process.env.BASE_URL}${meJpg.src}`,
}; };

View File

@ -2,7 +2,6 @@ import { Feed } from "feed";
import { getAllNotes } from "./parse-notes"; import { getAllNotes } from "./parse-notes";
import * as config from "../config"; import * as config from "../config";
import { meJpg } from "../config/favicons"; import { meJpg } from "../config/favicons";
import { RELEASE_DATE } from "../config/constants";
import type { GetServerSideProps } from "next"; import type { GetServerSideProps } from "next";
export type GetServerSideFeedProps = GetServerSideProps<Record<string, never>>; export type GetServerSideFeedProps = GetServerSideProps<Record<string, never>>;
@ -22,20 +21,20 @@ export const buildFeed = async (
// https://github.com/jpmonette/feed#example // https://github.com/jpmonette/feed#example
const feed = new Feed({ const feed = new Feed({
id: `${config.baseUrl}/`, id: `${process.env.BASE_URL}/`,
link: `${config.baseUrl}/`, link: `${process.env.BASE_URL}/`,
title: config.siteName, title: config.siteName,
description: config.longDescription, description: config.longDescription,
copyright: config.licenseUrl, copyright: config.licenseUrl,
updated: new Date(RELEASE_DATE), updated: new Date(process.env.RELEASE_DATE || Date.now()),
image: `${config.baseUrl}${meJpg.src}`, image: `${process.env.BASE_URL}${meJpg.src}`,
feedLinks: { feedLinks: {
rss: `${config.baseUrl}/feed.xml`, rss: `${process.env.BASE_URL}/feed.xml`,
atom: `${config.baseUrl}/feed.atom`, atom: `${process.env.BASE_URL}/feed.atom`,
}, },
author: { author: {
name: config.authorName, name: config.authorName,
link: `${config.baseUrl}/`, link: `${process.env.BASE_URL}/`,
email: config.authorEmail, email: config.authorEmail,
}, },
}); });
@ -48,11 +47,11 @@ export const buildFeed = async (
link: note.permalink, link: note.permalink,
title: note.title, title: note.title,
description: note.description, description: note.description,
image: note.image && `${config.baseUrl}${note.image}`, image: note.image && `${process.env.BASE_URL}${note.image}`,
author: [ author: [
{ {
name: config.authorName, name: config.authorName,
link: `${config.baseUrl}/`, link: `${process.env.BASE_URL}/`,
}, },
], ],
date: new Date(note.date), date: new Date(note.date),

View File

@ -1,7 +1,6 @@
import { serialize } from "next-mdx-remote/serialize"; import { serialize } from "next-mdx-remote/serialize";
import { minify } from "uglify-js"; import { minify } from "uglify-js";
import { getNoteData } from "./parse-notes"; import { getNoteData } from "./parse-notes";
import { IS_DEV_SERVER } from "../config/constants";
// remark/rehype markdown plugins // remark/rehype markdown plugins
import remarkGfm from "remark-gfm"; import remarkGfm from "remark-gfm";
@ -38,14 +37,15 @@ export const compileNote = async (slug: string): Promise<NoteWithSource> => {
// TODO: next-mdx-remote v4 doesn't (yet?) minify compiled JSX output, see: // TODO: next-mdx-remote v4 doesn't (yet?) minify compiled JSX output, see:
// https://github.com/hashicorp/next-mdx-remote/pull/211#issuecomment-1013658514 // https://github.com/hashicorp/next-mdx-remote/pull/211#issuecomment-1013658514
// ...so for now, let's do it manually (and conservatively) with uglify-js when building for production. // ...so for now, let's do it manually (and conservatively) with uglify-js when building for production.
const compiledSource = IS_DEV_SERVER const compiledSource =
? source.compiledSource process.env.NODE_ENV === "production"
: minify(source.compiledSource, { ? minify(source.compiledSource, {
toplevel: true, toplevel: true,
parse: { parse: {
bare_returns: true, bare_returns: true,
}, },
}).code; }).code
: source.compiledSource;
return { return {
frontMatter, frontMatter,

View File

@ -5,19 +5,20 @@ import pMap from "p-map";
import pMemoize from "p-memoize"; import pMemoize from "p-memoize";
import matter from "gray-matter"; import matter from "gray-matter";
import removeMarkdown from "remove-markdown"; import removeMarkdown from "remove-markdown";
import { marked } from "marked"; import { unified } from "unified";
// @ts-ignore import remarkParse from "remark-parse";
import { markedSmartypants } from "marked-smartypants"; import remarkRehype from "remark-rehype";
import rehypeStringify from "rehype-stringify";
import remarkSmartypants from "remark-smartypants";
import { formatDate } from "./format-date"; import { formatDate } from "./format-date";
import { baseUrl } from "../config";
import { NOTES_DIR } from "../config/constants";
import type { NoteFrontMatter } from "../../types"; import type { NoteFrontMatter } from "../../types";
import rehypeSanitize from "rehype-sanitize";
export const getNoteSlugs = async (): Promise<string[]> => { export const getNoteSlugs = async (): Promise<string[]> => {
// list all .mdx files in NOTES_DIR // list all .mdx files in "/notes"
const mdxFiles = await glob("*.mdx", { const mdxFiles = await glob("*.mdx", {
cwd: path.join(process.cwd(), NOTES_DIR), cwd: path.join(process.cwd(), "notes"),
dot: false, dot: false,
}); });
@ -34,13 +35,25 @@ export const getNoteData = async (
frontMatter: NoteFrontMatter; frontMatter: NoteFrontMatter;
content: string; content: string;
}> => { }> => {
const fullPath = path.join(process.cwd(), NOTES_DIR, `${slug}.mdx`); const fullPath = path.join(process.cwd(), "notes", `${slug}.mdx`);
const rawContent = await fs.readFile(fullPath, "utf8"); const rawContent = await fs.readFile(fullPath, "utf8");
const { data, content } = matter(rawContent); const { data, content } = matter(rawContent);
// attach marked extensions: // allow *very* limited markdown to be used in post titles
// https://marked.js.org/using_advanced#extensions const htmlTitle = String(
marked.use(markedSmartypants()); await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeSanitize, { tagNames: ["code", "em", "strong"] })
.use(remarkSmartypants, {
quotes: true,
dashes: "oldschool",
backticks: false,
ellipses: false,
})
.use(rehypeStringify, { allowDangerousHtml: true })
.process(data.title)
);
// return both the parsed YAML front matter (with a few amendments) and the raw, unparsed markdown content // return both the parsed YAML front matter (with a few amendments) and the raw, unparsed markdown content
return { return {
@ -48,15 +61,9 @@ export const getNoteData = async (
...(data as Partial<NoteFrontMatter>), ...(data as Partial<NoteFrontMatter>),
// zero markdown title: // zero markdown title:
title: removeMarkdown(data.title), title: removeMarkdown(data.title),
// allow markdown formatting to appear in post titles in some places (rarely used): htmlTitle,
htmlTitle: marked.parseInline(data.title, {
silent: true,
// these are deprecated and throw very noisy warnings but are still defaults, make it make sense...
mangle: false,
headerIds: false,
}),
slug, slug,
permalink: `${baseUrl}/${NOTES_DIR}/${slug}/`, permalink: `${process.env.BASE_URL}/notes/${slug}/`,
date: formatDate(data.date), // validate/normalize the date string provided from front matter date: formatDate(data.date), // validate/normalize the date string provided from front matter
}, },
content, content,

View File

@ -21,10 +21,9 @@ module.exports = (/** @type {string} */ phase) => {
: phase === PHASE_DEVELOPMENT_SERVER : phase === PHASE_DEVELOPMENT_SERVER
? `http://localhost:${process.env.PORT || 3000}` // https://nextjs.org/docs/api-reference/cli#development ? `http://localhost:${process.env.PORT || 3000}` // https://nextjs.org/docs/api-reference/cli#development
: `https://${config.siteDomain}`, // fallback to production url : `https://${config.siteDomain}`, // fallback to production url
// freeze build timestamp for when server-side pages need a "last updated" date: // 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.
RELEASE_DATE: new Date().toISOString(), RELEASE_DATE: new Date().toISOString(),
// check if we're running locally via `next dev`:
IS_DEV_SERVER: phase === PHASE_DEVELOPMENT_SERVER ? "true" : "",
}, },
images: { images: {
deviceSizes: [640, 750, 828, 1080, 1200, 1920], deviceSizes: [640, 750, 828, 1080, 1200, 1920],

View File

@ -24,7 +24,7 @@ const App = ({ Component, pageProps }: AppProps) => {
// get this page's URL with full domain, and hack around query parameters and anchors // get this page's URL with full domain, and hack around query parameters and anchors
// NOTE: this assumes trailing slashes are enabled in next.config.js // NOTE: this assumes trailing slashes are enabled in next.config.js
const canonical = `${config.baseUrl}${router.pathname === "/" ? "" : router.pathname}/`; const canonical = `${process.env.BASE_URL}${router.pathname === "/" ? "" : router.pathname}/`;
useEffect(() => { useEffect(() => {
// don't track pageviews on branch/deploy previews and localhost // don't track pageviews on branch/deploy previews and localhost

View File

@ -8,8 +8,8 @@ const Document = () => {
return ( return (
<Html lang={config.siteLocale} className={themeClassNames["light"]}> <Html lang={config.siteLocale} className={themeClassNames["light"]}>
<Head> <Head>
{/* inject a small script to set/restore the user's theme ASAP */} {/* inject this script (generated at build-time) to prioritize setting/restoring the user's theme. */}
<ThemeScript id="restore-theme" {...{ themeClassNames, themeStorageKey }} /> <ThemeScript key="restore-theme-js" {...{ themeClassNames, themeStorageKey }} />
<style id="stitches" dangerouslySetInnerHTML={{ __html: getCssText() }} /> <style id="stitches" dangerouslySetInnerHTML={{ __html: getCssText() }} />
</Head> </Head>

View File

@ -1,6 +1,5 @@
import { NextResponse } from "next/server"; import { NextResponse } from "next/server";
import queryString from "query-string"; import queryString from "query-string";
import { baseUrl } from "../../lib/config";
import type { NextRequest } from "next/server"; import type { NextRequest } from "next/server";
// fallback to dummy secret for testing: https://docs.hcaptcha.com/#integration-testing-test-keys // fallback to dummy secret for testing: https://docs.hcaptcha.com/#integration-testing-test-keys
@ -20,7 +19,7 @@ export const config = {
export default async (req: NextRequest) => { export default async (req: NextRequest) => {
// redirect GET requests to this endpoint to the contact form itself // redirect GET requests to this endpoint to the contact form itself
if (req.method === "GET") { if (req.method === "GET") {
return NextResponse.redirect(`${baseUrl}/contact/`); return NextResponse.redirect(`${process.env.BASE_URL}/contact/`);
} }
// possible weirdness? https://github.com/orgs/vercel/discussions/78#discussioncomment-5089059 // possible weirdness? https://github.com/orgs/vercel/discussions/78#discussioncomment-5089059

View File

@ -32,7 +32,7 @@ const Note = ({ frontMatter, source }: InferGetStaticPropsType<typeof getStaticP
}, },
images: [ images: [
{ {
url: `${config.baseUrl}${frontMatter.image || meJpg.src}`, url: `${process.env.BASE_URL}${frontMatter.image || meJpg.src}`,
alt: frontMatter.title, alt: frontMatter.title,
}, },
], ],
@ -47,7 +47,7 @@ const Note = ({ frontMatter, source }: InferGetStaticPropsType<typeof getStaticP
description={frontMatter.description || config.longDescription} description={frontMatter.description || config.longDescription}
datePublished={frontMatter.date} datePublished={frontMatter.date}
dateModified={frontMatter.date} dateModified={frontMatter.date}
images={[`${config.baseUrl}${frontMatter.image || meJpg.src}`]} images={[`${process.env.BASE_URL}${frontMatter.image || meJpg.src}`]}
{...articleJsonLd} {...articleJsonLd}
/> />

View File

@ -1,4 +1,3 @@
import { baseUrl } from "../lib/config";
import type { GetServerSideProps } from "next"; import type { GetServerSideProps } from "next";
export const getServerSideProps: GetServerSideProps<Record<string, never>> = async (context) => { export const getServerSideProps: GetServerSideProps<Record<string, never>> = async (context) => {
@ -12,7 +11,7 @@ ${
? `Disallow: /` ? `Disallow: /`
: `Allow: / : `Allow: /
Sitemap: ${baseUrl}/sitemap.xml` Sitemap: ${process.env.BASE_URL}/sitemap.xml`
} }
`; `;

View File

@ -1,12 +1,10 @@
import { SitemapStream, SitemapItemLoose, EnumChangefreq } from "sitemap"; import { SitemapStream, SitemapItemLoose, EnumChangefreq } from "sitemap";
import { getAllNotes } from "../lib/helpers/parse-notes"; import { getAllNotes } from "../lib/helpers/parse-notes";
import { baseUrl } from "../lib/config";
import { RELEASE_DATE, NOTES_DIR } from "../lib/config/constants";
import type { GetServerSideProps } from "next"; import type { GetServerSideProps } from "next";
export const getServerSideProps: GetServerSideProps<Record<string, never>> = async (context) => { export const getServerSideProps: GetServerSideProps<Record<string, never>> = async (context) => {
const { res } = context; const { res } = context;
const stream = new SitemapStream({ hostname: baseUrl }); const stream = new SitemapStream({ hostname: process.env.BASE_URL });
// cache on edge for 12 hours // cache on edge for 12 hours
res.setHeader("cache-control", "public, max-age=0, s-maxage=43200, stale-while-revalidate"); res.setHeader("cache-control", "public, max-age=0, s-maxage=43200, stale-while-revalidate");
@ -22,7 +20,7 @@ export const getServerSideProps: GetServerSideProps<Record<string, never>> = asy
url: "/", url: "/",
priority: 1.0, priority: 1.0,
changefreq: EnumChangefreq.WEEKLY, changefreq: EnumChangefreq.WEEKLY,
lastmod: RELEASE_DATE, // timestamp frozen when a new build is deployed lastmod: process.env.RELEASE_DATE, // timestamp frozen when a new build is deployed
}, },
{ url: "/birthday/" }, { url: "/birthday/" },
{ url: "/cli/" }, { url: "/cli/" },
@ -42,7 +40,7 @@ export const getServerSideProps: GetServerSideProps<Record<string, never>> = asy
const notes = await getAllNotes(); const notes = await getAllNotes();
notes.forEach((note) => { notes.forEach((note) => {
pages.push({ pages.push({
url: `/${NOTES_DIR}/${note.slug}/`, url: `/notes/${note.slug}/`,
// pull lastMod from front matter date // pull lastMod from front matter date
lastmod: note.date, lastmod: note.date,
}); });
@ -50,7 +48,7 @@ export const getServerSideProps: GetServerSideProps<Record<string, never>> = asy
// set lastmod of /notes/ page to most recent post's date // set lastmod of /notes/ page to most recent post's date
pages.push({ pages.push({
url: `/${NOTES_DIR}/`, url: `/notes/`,
lastmod: notes[0].date, lastmod: notes[0].date,
}); });