diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx
index 503c735..a8684fd 100644
--- a/src/app/blog/[slug]/page.tsx
+++ b/src/app/blog/[slug]/page.tsx
@@ -9,7 +9,7 @@ import config from "~/config";
export type PageProps = Promise<{ slug: string }>;
export async function generateStaticParams() {
- const posts = await getBlogPosts();
+ const posts = getBlogPosts();
return posts.map((post) => ({ slug: post.slug }));
}
diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx
index b378bdf..9ee4099 100644
--- a/src/app/blog/page.tsx
+++ b/src/app/blog/page.tsx
@@ -9,8 +9,8 @@ export const metadata = {
description: "A collection of insightful tutorials, guides, and various topics of interest by Jake McQuade.",
};
-export default function Blog() {
- const posts = await getBlogPosts();
+export default async function Blog() {
+ const posts = getBlogPosts();
return (
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index d021926..422f0d5 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,15 +1,16 @@
import type { Metadata, Viewport } from "next";
-import { Inter } from "next/font/google";
import { ReactNode } from "react";
+import { Inter } from "next/font/google";
import Script from "next/script";
import { ThemeProvider } from "~/components/theme";
// import MouseEffect from "~/components/mouse";
import Footer from "~/components/footer";
-import { cn } from "~/lib/utils";
import config from "~/config";
const font = Inter({ subsets: ["latin"], variable: "--font-sans" });
+
+
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
@@ -75,7 +76,7 @@ export default function RootLayout({ children }: Readonly<{ children: ReactNode
return (
-
+
{children}
diff --git a/src/components/backup.tsx b/src/components/backup.tsx
index 735c3a0..48b628c 100644
--- a/src/components/backup.tsx
+++ b/src/components/backup.tsx
@@ -6,14 +6,20 @@ import { FaArrowUp } from "react-icons/fa";
export default function BackUp() {
useEffect(() => {
const button = document.getElementById("top");
- window.onscroll = function () {
- if (document.documentElement.scrollTop > 80) {
- button?.classList.add("opacity-100");
- button?.classList.remove("opacity-0");
- } else {
- button?.classList.remove("opacity-100");
- button?.classList.add("opacity-0");
- }
+
+ const onScroll = () => {
+ if (!button) return;
+
+ const isVisible = document.documentElement.scrollTop > 80;
+ button.classList.toggle("opacity-100", isVisible);
+ button.classList.toggle("opacity-0", !isVisible);
+ };
+
+ window.addEventListener("scroll", onScroll, { passive: true });
+ onScroll();
+
+ return () => {
+ window.removeEventListener("scroll", onScroll);
};
}, []);
diff --git a/src/components/effects/blur-fade-text.tsx b/src/components/effects/blur-fade-text.tsx
index 903d1e4..539ff21 100644
--- a/src/components/effects/blur-fade-text.tsx
+++ b/src/components/effects/blur-fade-text.tsx
@@ -1,7 +1,7 @@
"use client";
import { cn } from "../../lib/utils";
-import { AnimatePresence, motion, Variants } from "motion/react";
+import { motion, Variants } from "motion/react";
import { useMemo } from "react";
interface BlurFadeTextProps {
@@ -37,45 +37,39 @@ const BlurFadeText = ({
if (animateByCharacter) {
return (
-
- {characters.map((char, i) => (
-
- {char}
-
- ))}
-
+ {characters.map((char, i) => (
+
+ {char}
+
+ ))}
);
}
return (
);
};
diff --git a/src/components/effects/blur-fade.tsx b/src/components/effects/blur-fade.tsx
index a101a76..b979365 100644
--- a/src/components/effects/blur-fade.tsx
+++ b/src/components/effects/blur-fade.tsx
@@ -1,7 +1,7 @@
"use client";
-import { AnimatePresence, motion, useInView, UseInViewOptions, Variants, MotionProps } from "motion/react";
-import { useRef } from "react";
+import { motion, useInView, UseInViewOptions, Variants, MotionProps } from "motion/react";
+import { useMemo, useRef } from "react";
type MarginType = UseInViewOptions["margin"];
@@ -37,38 +37,38 @@ export default function BlurFade({
const ref = useRef(null);
const inViewResult = useInView(ref, { once: true, margin: inViewMargin });
const isInView = !inView || inViewResult;
- const defaultVariants: Variants = {
- hidden: {
- [direction === "left" || direction === "right" ? "x" : "y"]:
- direction === "right" || direction === "down" ? -offset : offset,
- opacity: 0,
- filter: `blur(${blur})`,
- },
- visible: {
- [direction === "left" || direction === "right" ? "x" : "y"]: 0,
- opacity: 1,
- filter: `blur(0px)`,
- },
- };
- const combinedVariants = variant || defaultVariants;
+ const defaultVariants: Variants = useMemo(
+ () => ({
+ hidden: {
+ [direction === "left" || direction === "right" ? "x" : "y"]:
+ direction === "right" || direction === "down" ? -offset : offset,
+ opacity: 0,
+ filter: `blur(${blur})`,
+ },
+ visible: {
+ [direction === "left" || direction === "right" ? "x" : "y"]: 0,
+ opacity: 1,
+ filter: "blur(0px)",
+ },
+ }),
+ [blur, direction, offset],
+ );
+
return (
-
-
- {children}
-
-
+
+ {children}
+
);
}
diff --git a/src/lib/blog.ts b/src/lib/blog.ts
index 956710d..a45c267 100644
--- a/src/lib/blog.ts
+++ b/src/lib/blog.ts
@@ -23,33 +23,63 @@ function findRawBySlug(slug: string): string | null {
return null;
}
-export async function getPost(slug: string) {
+const metadataCache = new Map>();
+const postCache = new Map; slug: string }>>();
+let highlighterPromise: ReturnType | null = null;
+
+function getPostMetadata(slug: string) {
+ const cached = metadataCache.get(slug);
+ if (cached) return cached;
+
const raw = findRawBySlug(slug);
if (!raw) throw new Error(`Post not found: ${slug}`);
- const { content: rawContent, data: metadata } = matter(raw);
-
- const content = await unified()
- .use(remarkParse)
- .use(remarkRehype)
- .use(rehypePrettyCode, {
- // https://rehype-pretty.pages.dev/#usage
- keepBackground: false,
- theme: {
- light: "min-light",
- dark: "min-dark",
- },
- // IMPORTANT: avoid Shiki's default WASM Oniguruma engine on Cloudflare Workers
- getHighlighter: (options) =>
- createHighlighter({
- ...options,
- engine: createJavaScriptRegexEngine({ forgiving: true }),
- }),
- })
- .use(rehypeStringify)
- .process(rawContent);
-
- return { source: content.toString(), metadata, slug };
+ const { data: metadata } = matter(raw);
+ metadataCache.set(slug, metadata as Record);
+
+ return metadata as Record;
+}
+
+export async function getPost(slug: string) {
+ const cached = postCache.get(slug);
+ if (cached) return cached;
+
+ const postPromise = (async () => {
+ const raw = findRawBySlug(slug);
+ if (!raw) throw new Error(`Post not found: ${slug}`);
+
+ const { content: rawContent, data: metadata } = matter(raw);
+
+ const content = await unified()
+ .use(remarkParse)
+ .use(remarkRehype)
+ .use(rehypePrettyCode, {
+ // https://rehype-pretty.pages.dev/#usage
+ keepBackground: false,
+ theme: {
+ light: "min-light",
+ dark: "min-dark",
+ },
+ // IMPORTANT: avoid Shiki's default WASM Oniguruma engine on Cloudflare Workers
+ getHighlighter: (options) => {
+ if (!highlighterPromise) {
+ highlighterPromise = createHighlighter({
+ ...options,
+ engine: createJavaScriptRegexEngine({ forgiving: true }),
+ });
+ }
+
+ return highlighterPromise;
+ },
+ })
+ .use(rehypeStringify)
+ .process(rawContent);
+
+ return { source: content.toString(), metadata, slug };
+ })();
+
+ postCache.set(slug, postPromise);
+ return postPromise;
}
export function getBlogPosts() {
@@ -57,10 +87,5 @@ export function getBlogPosts() {
filePath.split("/").pop()!.replace(/\.mdx?$/, ""),
);
- return Promise.all(
- slugs.map(async (slug) => {
- const { metadata, source } = await getPost(slug);
- return { metadata, slug, source };
- }),
- );
+ return slugs.map((slug) => ({ metadata: getPostMetadata(slug), slug }));
}
diff --git a/src/styles/globals.css b/src/styles/globals.css
index 637998a..fab66f8 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -23,7 +23,7 @@
}
:root {
- font-family: Inter, sans-serif;
+ font-family: var(--font-sans), ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
font-feature-settings: "liga" 1, "calt" 1;
}
diff --git a/vite.config.ts b/vite.config.ts
index 1187e55..605c813 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -1,18 +1,17 @@
-
import { defineConfig } from "vite";
import vinext from "vinext";
import rsc from "@vitejs/plugin-rsc";
import { cloudflare } from "@cloudflare/vite-plugin";
export default defineConfig({
- build: {
- sourcemap: true,
- // Enable sourcemaps for better error reporting
-},
- plugins: [
- vinext(),
- cloudflare({
+ build: {
+ sourcemap: false,
+ },
+ plugins: [
+ vinext(),
+ rsc(),
+ cloudflare({
viteEnvironment: { name: "rsc", childEnvironments: ["ssr"] },
}),
],
-})
+});