In this repo is a simple implementation of a markdown static blog, a demo can be seen here, Features include:
- MDX Components
- Shadcn UI
- Pagination
- Dynamic Open Graph Image
- Syntax Highlighting in code block
Hopefully, from this project and the video, you can learn:
- NextJS 14 basics (layouts, app router etc)
- NextJS SEO
- NextJS Dynamic Graph Images
- Velite Setup and usage
- Shadcn/ui setup and usage
- Custom components in MDX
- Tailwind styling
You can follow along with Jolly Coding as we build this on YouTube.
Much of the design of this project is taken from shadcn in his projects, such as shadcn/ui and Taxonomy
✔ Which style would you like to use? › Default
✔ Which color would you like to use as base color? › Slate
✔ Would you like to use CSS variables for colors? … no / yes
// app/layout.tsx
import { cn } from "@/lib/utils";
<body
className={cn(
"min-h-screen bg-background font-sans antialiased",
inter.variable
)}
>
{children}
</body>;
// tailwind.config.ts
import { fontFamily } from "tailwindcss/defaultTheme";
...
extend: {
fontFamily: {
sans: ["var(--font-sans)", ...fontFamily.sans],
},
...
// vilite.config.ts
import { defineConfig, defineCollection, s } from "velite";
const computedFields = <T extends { slug: string }>(data: T) => ({
...data,
slugAsParams: data.slug.split("/").slice(1).join("/"),
});
const posts = defineCollection({
name: "Post",
pattern: "blog/**/*.mdx",
schema: s
.object({
slug: s.path(),
title: s.string().max(99),
description: s.string().max(999).optional(),
date: s.isodate(),
published: s.boolean().default(true),
body: s.mdx(),
})
.transform(computedFields),
});
export default defineConfig({
root: "content",
output: {
data: ".velite",
assets: "public/static",
base: "/static/",
name: "[name]-[hash:6].[ext]",
clean: true,
},
collections: { posts },
mdx: {
rehypePlugins: [],
remarkPlugins: [],
},
});
// next.config.mjs
import { build } from "velite";
/** @type {import('next').NextConfig} */
export default {
// othor next config here...
webpack: (config) => {
config.plugins.push(new VeliteWebpackPlugin());
return config;
},
};
class VeliteWebpackPlugin {
static started = false;
apply(/** @type {import('webpack').Compiler} */ compiler) {
// executed three times in nextjs
// twice for the server (nodejs / edge runtime) and once for the client
compiler.hooks.beforeCompile.tapPromise("VeliteWebpackPlugin", async () => {
if (VeliteWebpackPlugin.started) return;
VeliteWebpackPlugin.started = true;
const dev = compiler.options.mode === "development";
await build({ watch: dev, clean: !dev });
});
}
}
npx shadcn-ui@latest add sheet
// tailwind.config.ts
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
//mdx-components.tsx
const components = {
Image,
Callout,
};
import rehypeSlug from "rehype-slug";
import rehypePrettyCode from "rehype-pretty-code";
import rehypeAutolinkHeadings from "rehype-autolink-headings";
...
mdx: {
rehypePlugins: [
rehypeSlug,
[rehypePrettyCode, { theme: "github-dark" }],
[
rehypeAutolinkHeadings,
{
behavior: "wrap",
properties: {
className: ["subheading-anchor"],
ariaLabel: "Link to section",
},
},
],
],
remarkPlugins: [],
},
customize your own og playgroud
npm install github-slugger
npx shadcn-ui@latest add badge
npx shadcn-ui@latest add card