diff --git a/app/api/og/blog/route.tsx b/app/api/og/blog/route.tsx
new file mode 100644
index 0000000..2cf5b20
--- /dev/null
+++ b/app/api/og/blog/route.tsx
@@ -0,0 +1,268 @@
+/* eslint-disable @next/next/no-img-element */
+import { ImageResponse } from "next/og";
+
+async function loadGoogleFont(font: string, text: string) {
+ const url = `https://fonts.googleapis.com/css2?family=${font}:wght@600&text=${encodeURIComponent(text)}`;
+ const css = await (await fetch(url)).text();
+ const resource = css.match(
+ /src: url\((.+)\) format\('(opentype|truetype)'\)/,
+ );
+
+ if (resource) {
+ const response = await fetch(resource[1]);
+ if (response.status == 200) {
+ return await response.arrayBuffer();
+ }
+ }
+
+ throw new Error("failed to load font data");
+}
+
+export async function GET(request: Request) {
+ try {
+ const { searchParams } = new URL(request.url);
+
+ const title = searchParams.get("title");
+
+ return new ImageResponse(
+ (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {title}
+
+
+ ),
+ {
+ width: 1200,
+ height: 630,
+ fonts: [
+ {
+ name: "Geist",
+ weight: 600,
+ data: await loadGoogleFont("Geist", title || ""),
+ style: "normal",
+ },
+ ],
+ },
+ );
+ } catch (error: unknown) {
+ return new Response("Failed to generate OG image.", { status: 500 });
+ }
+}
diff --git a/app/blog/[slug]/page.tsx b/app/blog/[slug]/page.tsx
index 2db39ac..3d2668c 100644
--- a/app/blog/[slug]/page.tsx
+++ b/app/blog/[slug]/page.tsx
@@ -2,12 +2,12 @@ import React from "react";
// @components
import Link from "next/link";
-import Image from "next/image";
-import { Code } from "bright";
import { MDXRemote } from "next-mdx-remote/rsc";
import { CodeBlock } from "@/components/code-block";
import { Typography } from "@/components/typography";
import { BlogPostCard } from "@/components/blog-post-card";
+import { BlogCopyLink } from "@/components/blog-copy-link";
+import { BrowserWindow } from "@/components/browser-window";
// @icons
import { RiArrowLeftLine } from "@remixicon/react";
@@ -18,12 +18,55 @@ import matter from "gray-matter";
import remarkGfm from "remark-gfm";
import { formatDate } from "@/lib/utils";
import { notFound } from "next/navigation";
-import { BlogCopyLink } from "@/components/blog-copy-link";
+import rehypePrettyCode from "rehype-pretty-code";
+import { generateMetadata as generateMetadataFn } from "@/lib/utils";
+
+// @types
+import type { Metadata } from "next";
+
+const isProd = process.env.NODE_ENV === "production";
-Code.theme = {
- dark: "github-dark",
- light: "github-light",
-};
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ slug: string }>;
+}): Promise {
+ const { slug } = await params;
+ const { frontMatter } = await readMdxFile(slug);
+
+ const domain = new URL(
+ isProd
+ ? process.env.NEXT_PUBLIC_PROD_URL!
+ : process.env.NEXT_PUBLIC_DEV_URL!,
+ );
+
+ const imageUrl = `${domain}/api/og/blog?title=${encodeURIComponent(frontMatter.title)}`;
+
+ return generateMetadataFn({
+ title: frontMatter.title,
+ description: frontMatter.description,
+ keywords: frontMatter.keywords,
+ alternates: {
+ canonical: `${domain}/${slug}`,
+ },
+ openGraph: {
+ type: "article",
+ title: frontMatter.title,
+ description: frontMatter.description,
+ images: [
+ {
+ url: imageUrl,
+ },
+ ],
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: frontMatter.title,
+ description: frontMatter.description,
+ images: imageUrl,
+ },
+ });
+}
export default async function Post({
params,
@@ -46,7 +89,7 @@ export default async function Post({
return (
-
+
-
+
(
+ pre: (props) => (
-
+
),
+ a: (props) => (
+
+ ),
+ BrowserWindow,
}}
/>
@@ -103,7 +162,7 @@ export default async function Post({
<>
Continue your journey with these related posts
diff --git a/app/globals.css b/app/globals.css
index 3391134..ffb06a1 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -39,6 +39,12 @@
scrollbar-width: none;
}
-.code-block pre *::selection {
- @apply !bg-primary !text-white;
+code[data-theme],
+code[data-theme] span {
+ color: var(--shiki-light);
+}
+
+.dark code[data-theme],
+.dark code[data-theme] span {
+ color: var(--shiki-dark);
}
diff --git a/package.json b/package.json
index 755ab64..8a3a57f 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,6 @@
"@radix-ui/react-tooltip": "1.1.6",
"@remixicon/react": "4.6.0",
"@tailwindcss/postcss": "4.0.1",
- "bright": "1.0.0",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"gray-matter": "4.0.3",
@@ -34,8 +33,10 @@
"react-dom": "19.0.0",
"react-fast-marquee": "1.6.5",
"react-hook-form": "7.54.1",
+ "rehype-pretty-code": "0.14.0",
"remark-gfm": "4.0.0",
"resend": "4.0.1",
+ "shiki": "2.3.2",
"tailwind-merge": "2.5.5",
"usehooks-ts": "3.1.0",
"zod": "3.24.1"