├── .eslintrc.json ├── app ├── favicon.ico ├── robots.ts ├── page.tsx ├── sitemap.ts ├── layout.tsx ├── [...slug] │ └── page.tsx ├── api │ └── bookmark │ │ └── route.ts └── globals.css ├── public ├── image.webp ├── screenshot.jpg ├── logo.svg └── logo-dark.svg ├── postcss.config.js ├── lib ├── utils.ts ├── mdx.ts └── posts.ts ├── next.config.mjs ├── components ├── theme │ ├── theme-provider.tsx │ └── theme-toggle.tsx ├── site │ ├── footer.tsx │ ├── hero.tsx │ └── logo.tsx ├── ui │ ├── label.tsx │ ├── sonner.tsx │ ├── badge.tsx │ ├── button.tsx │ └── form.tsx ├── markdown │ ├── youtube.tsx │ ├── copy-article-button.tsx │ ├── share-button.tsx │ ├── meta.tsx │ ├── mdx-content.tsx │ ├── bookmark.tsx │ ├── code.tsx │ └── media.tsx ├── posts │ ├── list.tsx │ └── item.tsx └── ds.tsx ├── components.json ├── .gitignore ├── velite.config.ts ├── tsconfig.json ├── LICENSE ├── content ├── blog │ └── getting-started.mdx ├── docs │ └── installation.mdx └── example.mdx ├── package.json └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brijr/mdx/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/image.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brijr/mdx/HEAD/public/image.webp -------------------------------------------------------------------------------- /public/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brijr/mdx/HEAD/public/screenshot.jpg -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | experimental: { 4 | turbopackUseSystemTlsCerts: true, 5 | }, 6 | }; 7 | 8 | export default nextConfig; 9 | -------------------------------------------------------------------------------- /app/robots.ts: -------------------------------------------------------------------------------- 1 | import { MetadataRoute } from "next"; 2 | 3 | export default function robots(): MetadataRoute.Robots { 4 | const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com"; 5 | 6 | return { 7 | rules: { 8 | userAgent: "*", 9 | allow: "/", 10 | }, 11 | sitemap: `${baseUrl}/sitemap.xml`, 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /components/theme/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 5 | import { ThemeProviderProps } from "next-themes"; 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children}; 9 | } 10 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import { List } from "@/components/posts/list"; 2 | import { Hero } from "@/components/site/hero"; 3 | import { Main } from "@/components/ds"; 4 | 5 | import { getAllPosts } from "@/lib/posts"; 6 | 7 | export default function HomePage() { 8 | const posts = getAllPosts(); 9 | 10 | return ( 11 |
12 | 13 | 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /lib/mdx.ts: -------------------------------------------------------------------------------- 1 | export interface PageMeta { 2 | title: string; 3 | description?: string; 4 | date?: string; 5 | author?: string; 6 | tags?: string[]; 7 | image?: string; 8 | } 9 | 10 | export interface MDXPage { 11 | metadata: PageMeta; 12 | content: React.ReactNode; 13 | } 14 | 15 | export function formatDate(date: string) { 16 | return new Date(date).toLocaleDateString("en-US", { 17 | year: "numeric", 18 | month: "long", 19 | day: "numeric", 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "app/globals.css", 9 | "baseColor": "stone", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "iconLibrary": "lucide", 14 | "aliases": { 15 | "components": "@/components", 16 | "utils": "@/lib/utils", 17 | "ui": "@/components/ui", 18 | "lib": "@/lib", 19 | "hooks": "@/hooks" 20 | }, 21 | "registries": {} 22 | } 23 | -------------------------------------------------------------------------------- /components/site/footer.tsx: -------------------------------------------------------------------------------- 1 | import { Section, Container } from "@/components/ds"; 2 | 3 | export const Footer = () => { 4 | return ( 5 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | .velite 38 | 39 | # ai 40 | CLAUDE.md 41 | .claude 42 | -------------------------------------------------------------------------------- /components/site/hero.tsx: -------------------------------------------------------------------------------- 1 | import { Section, Container, Prose } from "@/components/ds"; 2 | import { Logo } from "@/components/site/logo"; 3 | 4 | export const Hero = () => { 5 | return ( 6 |
7 | 8 | 9 | 10 |

Markdown Blog Starter Template

11 |

12 | A modern MDX and Next.js starter made by{" "} 13 | brijr. Built with Next.js, Velite, 14 | and Tailwind CSS. View it on{" "} 15 | GitHub. 16 |

17 |
18 |
19 |
20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /app/sitemap.ts: -------------------------------------------------------------------------------- 1 | import { MetadataRoute } from "next"; 2 | import { getAllPosts } from "@/lib/posts"; 3 | 4 | export default function sitemap(): MetadataRoute.Sitemap { 5 | const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || "https://example.com"; 6 | 7 | // Homepage 8 | const routes: MetadataRoute.Sitemap = [ 9 | { 10 | url: baseUrl, 11 | lastModified: new Date(), 12 | changeFrequency: "daily", 13 | priority: 1, 14 | }, 15 | ]; 16 | 17 | // Add all published posts 18 | const posts = getAllPosts(); 19 | const postRoutes: MetadataRoute.Sitemap = posts.map((post) => ({ 20 | url: `${baseUrl}${post.permalink}`, 21 | lastModified: new Date(post.date), 22 | changeFrequency: "weekly" as const, 23 | priority: 0.8, 24 | })); 25 | 26 | return [...routes, ...postRoutes]; 27 | } 28 | -------------------------------------------------------------------------------- /components/theme/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as React from "react"; 4 | import { Moon, Sun } from "lucide-react"; 5 | import { useTheme } from "next-themes"; 6 | 7 | import { Button } from "@/components/ui/button"; 8 | 9 | export function ThemeToggle() { 10 | const { theme, setTheme } = useTheme(); 11 | 12 | const toggleTheme = () => { 13 | setTheme(theme === "dark" ? "light" : "dark"); 14 | }; 15 | 16 | return ( 17 | 22 | ); 23 | } 24 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /components/site/logo.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import Link from "next/link"; 3 | 4 | import LogoLight from "@/public/logo.svg"; 5 | import LogoDark from "@/public/logo-dark.svg"; 6 | 7 | export const Logo = ({ 8 | href = "/", 9 | width = 72, 10 | height, 11 | className, 12 | }: { 13 | href?: string; 14 | width?: number; 15 | height?: number; 16 | className?: string; 17 | }) => { 18 | return ( 19 | 20 | Italy Vita Logo 27 | Italy Vita Logo 34 | 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /components/markdown/youtube.tsx: -------------------------------------------------------------------------------- 1 | import { YouTubeEmbed } from "@next/third-parties/google"; 2 | import { cn } from "@/lib/utils"; 3 | 4 | interface YouTubeProps { 5 | videoid: string; 6 | width?: number | string; 7 | height?: number; 8 | params?: string; 9 | playlabel?: string; 10 | className?: string; 11 | } 12 | 13 | export function YouTube({ 14 | videoid, 15 | width = "100%", 16 | height, 17 | params, 18 | playlabel, 19 | className, 20 | }: YouTubeProps) { 21 | return ( 22 |
28 | 35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /velite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, defineCollection, s } from "velite"; 2 | 3 | const posts = defineCollection({ 4 | name: "Post", 5 | pattern: "**/*.mdx", 6 | schema: s 7 | .object({ 8 | slug: s.path(), 9 | title: s.string().max(99), 10 | description: s.string().max(999).optional(), 11 | date: s.isodate(), 12 | author: s.string().optional(), 13 | tags: s.array(s.string()).optional(), 14 | published: s.boolean().default(true), 15 | body: s.mdx(), 16 | }) 17 | .transform((data) => ({ 18 | ...data, 19 | permalink: `/${data.slug}`, 20 | })), 21 | }); 22 | 23 | export default defineConfig({ 24 | root: "content", 25 | output: { 26 | data: ".velite", 27 | assets: "public/static", 28 | base: "/static/", 29 | name: "[name]-[hash:6].[ext]", 30 | clean: true, 31 | }, 32 | collections: { posts }, 33 | mdx: { 34 | rehypePlugins: [], 35 | remarkPlugins: [], 36 | }, 37 | }); 38 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": [ 4 | "dom", 5 | "dom.iterable", 6 | "esnext" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "strict": true, 11 | "noEmit": true, 12 | "esModuleInterop": true, 13 | "module": "esnext", 14 | "moduleResolution": "bundler", 15 | "resolveJsonModule": true, 16 | "isolatedModules": true, 17 | "jsx": "react-jsx", 18 | "incremental": true, 19 | "plugins": [ 20 | { 21 | "name": "next" 22 | } 23 | ], 24 | "paths": { 25 | "@/*": [ 26 | "./*" 27 | ], 28 | ".velite": [ 29 | "./.velite" 30 | ], 31 | "#site/content": [ 32 | "./.velite/index.d.ts" 33 | ] 34 | }, 35 | "target": "ES2017" 36 | }, 37 | "include": [ 38 | "next-env.d.ts", 39 | "**/*.ts", 40 | "**/*.tsx", 41 | ".next/types/**/*.ts", 42 | "app/(markdown)/page.mdx", 43 | ".next/dev/types/**/*.ts", 44 | ".velite" 45 | ], 46 | "exclude": [ 47 | "node_modules" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Bridger Tower 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { 4 | CircleCheckIcon, 5 | InfoIcon, 6 | Loader2Icon, 7 | OctagonXIcon, 8 | TriangleAlertIcon, 9 | } from "lucide-react" 10 | import { useTheme } from "next-themes" 11 | import { Toaster as Sonner, type ToasterProps } from "sonner" 12 | 13 | const Toaster = ({ ...props }: ToasterProps) => { 14 | const { theme = "system" } = useTheme() 15 | 16 | return ( 17 | , 22 | info: , 23 | warning: , 24 | error: , 25 | loading: , 26 | }} 27 | style={ 28 | { 29 | "--normal-bg": "var(--popover)", 30 | "--normal-text": "var(--popover-foreground)", 31 | "--normal-border": "var(--border)", 32 | "--border-radius": "var(--radius)", 33 | } as React.CSSProperties 34 | } 35 | {...props} 36 | /> 37 | ) 38 | } 39 | 40 | export { Toaster } 41 | -------------------------------------------------------------------------------- /components/posts/list.tsx: -------------------------------------------------------------------------------- 1 | import { Section, Container } from "@/components/ds"; 2 | import { Item } from "@/components/posts/item"; 3 | 4 | import type { Post } from "#site/content"; 5 | 6 | export const List = ({ posts }: { posts: Post[] }) => { 7 | return ( 8 |
9 | 10 |

Recent Posts

11 | {posts.length > 0 ? ( 12 |
    13 | {posts.map((post) => ( 14 | 22 | ))} 23 |
24 | ) : ( 25 | 26 | )} 27 |
28 |
29 | ); 30 | }; 31 | 32 | const NoPosts = () => { 33 | return ( 34 |

35 | No posts yet. Create your first post in the{" "} 36 | 37 | content/ 38 | 39 | directory. 40 |

41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ThemeProvider } from "@/components/theme/theme-provider"; 2 | import { Layout, Main } from "@/components/ds"; 3 | import { ThemeToggle } from "@/components/theme/theme-toggle"; 4 | import { Toaster } from "@/components/ui/sonner"; 5 | import { Footer } from "@/components/site/footer"; 6 | 7 | import type { Metadata } from "next"; 8 | 9 | import "./globals.css"; 10 | 11 | import { cn } from "@/lib/utils"; 12 | 13 | export const metadata: Metadata = { 14 | title: { 15 | default: "MDX Starter Template for Building Websites", 16 | template: "%s | MDX Starter", 17 | }, 18 | description: 19 | "MDX and Next.js Starter made by Bridger Tower at 9d8 and WIP / AC", 20 | }; 21 | 22 | export default function RootLayout({ 23 | children, 24 | }: { 25 | children: React.ReactNode; 26 | }) { 27 | return ( 28 | 29 | 34 | 40 |
{children}
41 |