Next.js MDX Design System
Create three types of beautiful, production-ready documents using Next.js 15+, MDX, and shadcn/ui:
- Academic Articles - Distill-inspired design with margin notes, citations, and elegant typography
- Data Blog Posts - Modern web design with hero sections, feature grids, and interactive elements
- Interactive Presentations - Slide-based presentations with keyboard navigation and transitions
Triggers for Academic Articles:
- "Create an academic paper/article/research document"
- "I need a paper with citations and margin notes"
- "Make a scientific/research publication"
- Requests for formal academic writing with references
Triggers for Blog Posts:
- "Create a blog post/tutorial/guide"
- "Make a modern web article"
- "I need a data analysis blog"
- Requests for web-first content with modern design
Triggers for Presentations:
- "Create a presentation/slide deck"
- "Make slides for a talk"
- "I need an interactive presentation"
- Requests for slide-based content with navigation
Required dependencies:
npm install next@latest react@latest react-dom@latest
npm install @next/mdx @mdx-js/loader @mdx-js/react @mdx-js/mdx
npm install remark-gfm remark-math rehype-katex rehype-pretty-code
npm install -D @tailwindcss/typography
npx shadcn@latest init
npx shadcn@latest add button card tabs badge separator
npm install framer-motion
System requirements:
- Node.js 18+
- Next.js 15+
- React 19+
- TypeScript 5+
References (references/)
Load these when implementing specific features or needing detailed guidance:
- quick-reference.md - Common patterns, components, and MDX syntax
- troubleshooting.md - Common issues and solutions
Assets (assets/)
Production-ready templates for each document type:
-
academic/ - Distill-inspired academic article template
page.mdx - Article content template
layout.tsx - Academic layout with margin notes
components/Callout.tsx - Callout boxes
components/Citation.tsx - Citation components
components/MarginNote.tsx - Margin note component
-
blog/ - Modern blog post template
page.mdx - Blog content template
layout.tsx - Blog layout
components/Hero.tsx - Hero section component
components/FeatureGrid.tsx - Feature grid component
-
presentation/ - Interactive presentation template
page.mdx - Presentation content template
layout.tsx - Presentation layout with navigation
components/Slide.tsx - Slide component
components/TwoColumn.tsx - Two-column layout component
-
globals.css - Global styles and Tailwind configuration
1. Project Setup
npx create-next-app@latest my-project --typescript --tailwind --app
cd my-project
npm install @next/mdx @mdx-js/loader @mdx-js/react @mdx-js/mdx
npm install remark-gfm remark-math rehype-katex rehype-pretty-code
npm install -D @tailwindcss/typography
npx shadcn@latest init
npx shadcn@latest add button card tabs badge separator
npm install framer-motion
import createMDX from '@next/mdx'
import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
import rehypeKatex from 'rehype-katex'
import rehypePrettyCode from 'rehype-pretty-code'
const nextConfig = {
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],
experimental: {
mdxRs: true,
},
}
const withMDX = createMDX({
options: {
remarkPlugins: [remarkGfm, remarkMath],
rehypePlugins: [
rehypeKatex,
[rehypePrettyCode, {
theme: 'github-dark',
keepBackground: false,
}],
],
},
})
export default withMDX(nextConfig)
import type { Config } from "tailwindcss";
const config: Config = {
darkMode: ["class"],
content: [
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
typography: {
DEFAULT: {
css: {
maxWidth: 'none',
},
},
},
},
},
plugins: [require("@tailwindcss/typography")],
};
export default config;
1. Academic Articles (Distill-Inspired)
Design Features:
- Narrow content column (760px max-width) with wide margins (300px)
- Source Serif 4 typography for elegant readability
- Margin notes positioned absolutely on desktop, inline on mobile
- Citation system with hover tooltips
- Callout boxes for important information
- Article metadata (authors, affiliations, ORCID, DOI)
- Responsive table of contents
- LaTeX math support via KaTeX
File Structure:
app/
└── (academic)/ # Route group (URL won't include "academic")
└── my-article/
└── page.mdx # Your article content
Typography:
- Font: Source Serif 4 (serif)
- Body: 21px / 1.58 line-height
- Headings: Bold, carefully sized hierarchy
Components:
MarginNote - Side notes in margins
Citation - Inline citations with hover cards
Callout - Highlighted information boxes
ArticleHeader - Author/affiliation metadata
TableOfContents - Auto-generated from headings
2. Data Blog Posts
Design Features:
- Modern gradient hero sections
- Feature card grids for highlighting key points
- shadcn/ui components (Tabs, Buttons, Cards, Badges)
- Code syntax highlighting with rehype-pretty-code
- Collapsible sections for long content
- Inter typography for clean, modern look
File Structure:
app/
└── (blog)/ # Route group
└── my-post/
└── page.mdx # Your blog post
Typography:
Components:
Hero - Gradient hero sections with CTAs
FeatureGrid - Card-based feature highlights
CodeBlock - Syntax-highlighted code
- shadcn/ui components
3. Interactive Presentations
Design Features:
- Full-screen slide navigation
- Keyboard controls (arrow keys, space, f for fullscreen)
- Slide counter and navigation UI
- Multiple layout options (gradient, dark, light backgrounds)
- Two-column layouts for content + visuals
- Progressive disclosure support (with Framer Motion)
File Structure:
app/
└── (presentation)/ # Route group
└── my-talk/
└── page.mdx # Your slides
Typography:
- Font: Inter or custom presentation fonts
- Large text sizes (3xl-7xl) for visibility
- High contrast for readability
Components:
Slide - Individual slide container with ID-based navigation
TwoColumn - Split slide into two columns
- Presentation navigation controls
Academic Article Setup
1. Create Layout:
import type { Metadata } from 'next'
import { Source_Serif_4 } from 'next/font/google'
import '@/app/globals.css'
const sourceSerif = Source_Serif_4({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Academic Articles',
description: 'Research publications',
}
export default function AcademicLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className={sourceSerif.className}>
<main className="min-h-screen bg-white">
{children}
</main>
</div>
)
}
2. Create MarginNote Component:
'use client'
interface MarginNoteProps {
children: React.ReactNode
number?: number
}
export function MarginNote({ children, number }: MarginNoteProps) {
return (
<span className="margin-note-wrapper relative">
{number && (
<sup className="text-blue-600 cursor-help">{number}</sup>
)}
<span className="margin-note absolute left-[calc(100%+2rem)] w-[280px] text-sm text-gray-600 leading-relaxed hidden lg:block">
{children}
</span>
<span className="lg:hidden inline-block mt-2 px-4 py-2 bg-blue-50 text-sm text-gray-700 rounded-md border-l-4 border-blue-400">
{children}
</span>
</span>
)
}
3. Create Citation Component:
'use client'
import { useState } from 'react'
interface CitationProps {
id: string
children: React.ReactNode
reference: string
}
export function Citation({ id, children, reference }: CitationProps) {
const [showTooltip, setShowTooltip] = useState(false)
return (
<span className="relative inline">
<span
className="text-blue-600 cursor-help hover:underline"
onMouseEnter={() => setShowTooltip(true)}
onMouseLeave={() => setShowTooltip(false)}
>
{children}
</span>
{showTooltip && (
<span className="absolute bottom-full left-0 mb-2 w-64 p-3 bg-gray-900 text-white text-xs rounded-lg shadow-lg z-10">
{reference}
</span>
)}
</span>
)
}
4. Create Callout Component:
import { ReactNode } from 'react'
interface CalloutProps {
children: ReactNode
type?: 'info' | 'warning' | 'success' | 'error'
title?: string
}
export function Callout({ children, type = 'info', title }: CalloutProps) {
const styles = {
info: 'bg-blue-50 border-blue-200 text-blue-900',
warning: 'bg-yellow-50 border-yellow-200 text-yellow-900',
success: 'bg-green-50 border-green-200 text-green-900',
error: 'bg-red-50 border-red-200 text-red-900',
}
return (
<div className={`my-6 p-4 border-l-4 rounded-r-lg ${styles[type]}`}>
{title && <div className="font-bold mb-2">{title}</div>}
<div className="text-sm">{children}</div>
</div>
)
}
5. Create Article Header:
interface Author {
name: string
affiliation: string
orcid?: string
}
interface ArticleHeaderProps {
title: string
authors: Author[]
abstract: string
doi?: string
date?: string
}
export function ArticleHeader({ title, authors, abstract, doi, date }: ArticleHeaderProps) {
return (
<header className="mb-12 pb-8 border-b border-gray-200">
<h1 className="text-5xl font-bold mb-6 leading-tight">{title}</h1>
<div className="mb-6 space-y-2">
{authors.map((author, idx) => (
<div key={idx} className="text-gray-700">
<span className="font-semibold">{author.name}</span>
{author.orcid && (
<a href={`https://orcid.org/${author.orcid}`} className="ml-2 text-blue-600 hover:underline text-sm">
ORCID
</a>
)}
<div className="text-sm text-gray-600">{author.affiliation}</div>
</div>
))}
</div>
{date && <div className="text-sm text-gray-600 mb-4">{date}</div>}
{doi && (
<div className="text-sm text-gray-600 mb-6">
DOI: <a href={`https://doi.org/${doi}`} className="text-blue-600 hover:underline">{doi}</a>
</div>
)}
<div className="bg-gray-50 p-6 rounded-lg">
<h2 className="text-xl font-bold mb-3">Abstract</h2>
<p className="text-gray-700 leading-relaxed">{abstract}</p>
</div>
</header>
)
}
6. Create Example Article:
// app/(academic)/my-article/page.mdx
import { ArticleHeader } from '@/components/academic/ArticleHeader'
import { MarginNote } from '@/components/academic/MarginNote'
import { Citation } from '@/components/academic/Citation'
import { Callout } from '@/components/academic/Callout'
<div className="max-w-[760px] mx-auto px-6 py-12 relative">
<ArticleHeader
title="The Impact of Supply Chain Disruptions on Global Trade"
authors={[
{ name: "John Doe", affiliation: "Economics Department, University", orcid: "0000-0000-0000-0000" },
{ name: "Jane Smith", affiliation: "Business School, University" }
]}
abstract="This paper examines how supply chain disruptions impact global trade patterns. We employ a triple-difference identification strategy..."
doi="10.1234/example.2024"
date="October 2024"
/>
## Introduction
The global supply chain has become increasingly complex<MarginNote>Recent studies show over 90% of Fortune 500 companies experienced supply chain disruptions in the past year.</MarginNote> and interconnected in recent decades.
<Citation id="smith2023" reference="Smith, J. (2023). Supply Chain Resilience. Journal of Economics.">
Previous research has documented
</Citation> the vulnerability of just-in-time manufacturing systems.
<Callout type="info" title="Key Finding">
Our analysis reveals a 23% increase in shipping times during disruption periods, with cascading effects throughout the supply network.
</Callout>
## Methodology
We employ a **shift-share triple-difference** design that leverages variation in:
1. Industry exposure to disrupted routes
2. Regional dependence on affected ports
3. Timing of disruption events
The identification strategy follows:
$$
y_{irt} = \alpha + \beta_1 Disruption_{rt} + \beta_2 Exposure_{ir} + \beta_3 (Disruption_{rt} \times Exposure_{ir}) + \gamma_{it} + \delta_{rt} + \epsilon_{irt}
$$
<MarginNote>This specification includes industry-time and region-time fixed effects to control for confounding factors.</MarginNote>
## Data
Our analysis combines:
- **Port performance metrics** from Vizion (2020-2024)
- **Trade flows** from UN Comtrade
- **Shipping routes** from Lloyd's List Intelligence
<Callout type="warning">
Data limitations: Some developing economies lack comprehensive port-level data before 2021.
</Callout>
## Results
Table 1 presents our main findings. The disruption effect is statistically significant (p < 0.01) and economically meaningful.
| Variable | Coefficient | Std. Error | p-value |
|----------|-------------|------------|---------|
| Disruption × Exposure | -0.234 | 0.045 | <0.001 |
| Disruption | -0.123 | 0.032 | <0.001 |
| Exposure | 0.089 | 0.028 | 0.002 |
<MarginNote number={1}>All standard errors are clustered at the region-industry level to account for serial correlation.</MarginNote>
## Conclusion
Our findings demonstrate that supply chain disruptions have persistent effects on trade patterns, lasting well beyond the immediate disruption period.
</div>
Blog Post Setup
1. Create Layout:
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import '@/app/globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Blog',
description: 'Data analysis and insights',
}
export default function BlogLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className={inter.className}>
<main className="min-h-screen bg-white">
{children}
</main>
</div>
)
}
2. Create Hero Component:
import { Button } from '@/components/ui/button'
import Link from 'next/link'
interface HeroProps {
title: string
description: string
primaryAction?: {
label: string
href: string
}
secondaryAction?: {
label: string
href: string
}
}
export function Hero({ title, description, primaryAction, secondaryAction }: HeroProps) {
return (
<div className="relative overflow-hidden bg-gradient-to-br from-blue-600 via-purple-600 to-pink-600 text-white">
<div className="absolute inset-0 bg-black/20"></div>
<div className="relative max-w-7xl mx-auto px-6 py-24 sm:py-32">
<div className="max-w-3xl">
<h1 className="text-5xl sm:text-6xl font-bold mb-6 leading-tight">
{title}
</h1>
<p className="text-xl sm:text-2xl mb-8 text-white/90 leading-relaxed">
{description}
</p>
<div className="flex flex-wrap gap-4">
{primaryAction && (
<Button asChild size="lg" className="bg-white text-purple-600 hover:bg-gray-100">
<Link href={primaryAction.href}>{primaryAction.label}</Link>
</Button>
)}
{secondaryAction && (
<Button asChild size="lg" variant="outline" className="border-white text-white hover:bg-white/10">
<Link href={secondaryAction.href}>{secondaryAction.label}</Link>
</Button>
)}
</div>
</div>
</div>
</div>
)
}
3. Create FeatureGrid Component:
import { Card } from '@/components/ui/card'
import { ReactNode } from 'react'
interface Feature {
icon?: ReactNode
title: string
description: string
}
interface FeatureGridProps {
features: Feature[]
}
export function FeatureGrid({ features }: FeatureGridProps) {
return (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 my-12">
{features.map((feature, idx) => (
<Card key={idx} className="p-6 hover:shadow-lg transition-shadow">
{feature.icon && (
<div className="mb-4 text-blue-600">
{feature.icon}
</div>
)}
<h3 className="text-xl font-bold mb-2">{feature.title}</h3>
<p className="text-gray-600 leading-relaxed">{feature.description}</p>
</Card>
))}
</div>
)
}
4. Create Example Blog Post:
// app/(blog)/my-post/page.mdx
import { Hero } from '@/components/blog/Hero'
import { FeatureGrid } from '@/components/blog/FeatureGrid'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
<Hero
title="Analyzing Port Performance with Modern Data Tools"
description="Learn how to visualize and analyze shipping data using Observable Plot, Quarto, and Python"
primaryAction={{ label: "Get Started", href: "#start" }}
secondaryAction={{ label: "View Demo", href: "#demo" }}
/>
<div className="max-w-4xl mx-auto px-6 py-12">
<div className="flex gap-2 mb-8">
<Badge>Data Analysis</Badge>
<Badge variant="secondary">Python</Badge>
<Badge variant="outline">Observable Plot</Badge>
</div>
## Introduction
Modern supply chain analytics requires powerful visualization tools. In this guide, we'll explore how to analyze port performance metrics using cutting-edge data tools.
<FeatureGrid
features={[
{
title: "Interactive Visualizations",
description: "Create responsive charts that adapt to your data with Observable Plot"
},
{
title: "Reproducible Analysis",
description: "Use Quarto to combine code, data, and narrative in one document"
},
{
title: "Python Integration",
description: "Leverage pandas and pyobsplot for seamless data processing"
}
]}
/>
## Getting Started {#start}
First, install the required packages:
```bash
pip install pandas pyobsplot polars
Then import your dependencies:
import pandas as pd
import pyobsplot as op
df = pd.read_parquet("port_metrics.parquet")
Let's analyze dwell times across different ports:
<Tabs defaultValue="code" className="my-8">
<TabsList>
<TabsTrigger value="code">Code</TabsTrigger>
<TabsTrigger value="output">Output</TabsTrigger>
</TabsList>
<TabsContent value="code">
```python
# Calculate average dwell times by port
avg_dwell = df.groupby('port_name')['dwell_time'].mean()
# Create visualization
op.Plot.plot({
"marks": [
op.Plot.barY(avg_dwell, {
"x": "port_name",
"y": "dwell_time",
"fill": "steelblue"
})
]
})
```
</TabsContent>
<TabsContent value="output">
<div className="p-4 bg-gray-50 rounded-lg">
Chart would appear here
</div>
</TabsContent>
</Tabs>
For more complex analysis, you can:
- Combine multiple data sources - Join port metrics with trade data
- Create faceted visualizations - Compare across regions or time periods
- Add interactivity - Use Observable Plot's built-in interactions
<div className="my-8 p-6 bg-gradient-to-r from-purple-50 to-pink-50 rounded-xl">
<h3 className="text-2xl font-bold mb-4">Next Steps</h3>
<p className="mb-4 text-gray-700">
Ready to dive deeper? Check out our advanced tutorials on econometric analysis and causal inference.
</p>
<Button>Explore Tutorials</Button>
</div>
Modern data tools make it easier than ever to analyze complex supply chain data. With Observable Plot and Python, you can create publication-ready visualizations in minutes.
</div>
```
Presentation Setup
1. Create Layout:
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import '@/app/globals.css'
const inter = Inter({ subsets: ['latin'] })
export const metadata: Metadata = {
title: 'Presentation',
description: 'Interactive slides',
}
export default function PresentationLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className={inter.className}>
<main className="min-h-screen bg-gray-900">
{children}
</main>
</div>
)
}
2. Create Slide Component:
'use client'
import { ReactNode, useEffect, useState } from 'react'
interface SlideProps {
id: number
children: ReactNode
background?: 'white' | 'dark' | 'gradient'
className?: string
}
export function Slide({ id, children, background = 'white', className = '' }: SlideProps) {
const [currentSlide, setCurrentSlide] = useState(0)
const [totalSlides, setTotalSlides] = useState(0)
useEffect(() => {
const slides = document.querySelectorAll('[data-slide]')
setTotalSlides(slides.length)
const hash = window.location.hash.replace('#', '')
const initialSlide = hash ? parseInt(hash) : 0
setCurrentSlide(initialSlide)
const slide = document.querySelector(`[data-slide="${currentSlide}"]`)
slide?.scrollIntoView({ behavior: 'smooth' })
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'ArrowRight' || e.key === ' ') {
e.preventDefault()
const next = Math.min(currentSlide + 1, totalSlides - 1)
setCurrentSlide(next)
window.location.hash = `#${next}`
} else if (e.key === 'ArrowLeft') {
e.preventDefault()
const prev = Math.max(currentSlide - 1, 0)
setCurrentSlide(prev)
window.location.hash = `#${prev}`
} else if (e.key === 'f') {
document.documentElement.requestFullscreen()
}
}
window.addEventListener('keydown', handleKeyDown)
return () => window.removeEventListener('keydown', handleKeyDown)
}, [currentSlide, totalSlides])
const bgStyles = {
white: 'bg-white text-gray-900',
dark: 'bg-gray-900 text-white',
gradient: 'bg-gradient-to-br from-blue-600 via-purple-600 to-pink-600 text-white',
}
const isActive = id === currentSlide
return (
<div
data-slide={id}
className={`
min-h-screen w-full flex items-center justify-center p-12
${bgStyles[background]}
${className}
${isActive ? 'opacity-100' : 'opacity-40'}
`}
>
<div className="max-w-6xl w-full">
{children}
<div className="absolute bottom-8 right-8 text-sm opacity-50">
{id + 1} / {totalSlides}
</div>
</div>
</div>
)
}
3. Create TwoColumn Component:
import { ReactNode } from 'react'
interface TwoColumnProps {
left: ReactNode
right: ReactNode
}
export function TwoColumn({ left, right }: TwoColumnProps) {
return (
<div className="grid grid-cols-2 gap-12 items-center">
<div>{left}</div>
<div>{right}</div>
</div>
)
}
4. Create Example Presentation:
// app/(presentation)/my-talk/page.mdx
import { Slide } from '@/components/presentation/Slide'
import { TwoColumn } from '@/components/presentation/TwoColumn'
<Slide id={0} background="gradient">
<h1 className="text-7xl font-bold mb-6">Supply Chain Analytics</h1>
<p className="text-3xl text-white/90">Modern Tools for Data-Driven Decisions</p>
<p className="text-xl mt-12 text-white/70">Use arrow keys to navigate →</p>
</Slide>
<Slide id={1} background="white">
<h2 className="text-6xl font-bold mb-12">Today's Agenda</h2>
<div className="text-3xl space-y-6">
<div>1. The Challenge</div>
<div>2. Our Approach</div>
<div>3. Results & Impact</div>
<div>4. Next Steps</div>
</div>
</Slide>
<Slide id={2} background="dark">
<TwoColumn
left={
<>
<h2 className="text-5xl font-bold mb-8">The Challenge</h2>
<ul className="text-2xl space-y-4 text-gray-300">
<li>Complex global supply chains</li>
<li>Real-time disruptions</li>
<li>Limited visibility</li>
<li>Reactive decision-making</li>
</ul>
</>
}
right={
<div className="p-12 bg-red-900/30 rounded-2xl border-2 border-red-500">
<div className="text-7xl font-bold text-red-400 mb-4">23%</div>
<div className="text-2xl text-gray-300">Increase in disruption frequency</div>
</div>
}
/>
</Slide>
<Slide id={3} background="white">
<h2 className="text-6xl font-bold mb-12">Our Solution</h2>
<div className="grid grid-cols-3 gap-8">
<div className="p-8 bg-blue-50 rounded-xl">
<div className="text-5xl mb-4">📊</div>
<h3 className="text-2xl font-bold mb-4">Data Integration</h3>
<p className="text-gray-600">Combine multiple sources into a unified view</p>
</div>
<div className="p-8 bg-purple-50 rounded-xl">
<div className="text-5xl mb-4">🔍</div>
<h3 className="text-2xl font-bold mb-4">Real-Time Analysis</h3>
<p className="text-gray-600">Monitor performance as it happens</p>
</div>
<div className="p-8 bg-pink-50 rounded-xl">
<div className="text-5xl mb-4">🚀</div>
<h3 className="text-2xl font-bold mb-4">Predictive Models</h3>
<p className="text-gray-600">Anticipate disruptions before they occur</p>
</div>
</div>
</Slide>
<Slide id={4} background="gradient">
<TwoColumn
left={
<>
<h2 className="text-5xl font-bold mb-8">Key Results</h2>
<div className="space-y-6">
<div className="p-6 bg-white/10 rounded-xl backdrop-blur">
<div className="text-4xl font-bold mb-2">-34%</div>
<div className="text-xl">Reduction in dwell time</div>
</div>
<div className="p-6 bg-white/10 rounded-xl backdrop-blur">
<div className="text-4xl font-bold mb-2">+28%</div>
<div className="text-xl">Improvement in on-time delivery</div>
</div>
<div className="p-6 bg-white/10 rounded-xl backdrop-blur">
<div className="text-4xl font-bold mb-2">$2.3M</div>
<div className="text-xl">Annual cost savings</div>
</div>
</div>
</>
}
right={
<div className="text-2xl leading-relaxed text-white/90">
By implementing our analytics platform, we achieved measurable improvements across all key metrics within 6 months.
</div>
}
/>
</Slide>
<Slide id={5} background="dark">
<h2 className="text-6xl font-bold mb-8 text-center">Thank You</h2>
<p className="text-3xl text-center text-gray-400 mb-12">Questions?</p>
<div className="text-center text-xl text-gray-500">
<div>john.doe@example.com</div>
<div className="mt-2">@johndoe</div>
</div>
</Slide>
Create a globals.css file with these styles:
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
@import url('https://fonts.googleapis.com/css2?family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
@import url('https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css');
html {
scroll-behavior: smooth;
}
body {
@apply antialiased;
}
}
@layer components {
.academic-article {
@apply max-w-[760px] mx-auto px-6 py-12 relative;
}
.academic-article h1 {
@apply text-5xl font-bold mb-6 leading-tight;
}
.academic-article h2 {
@apply text-4xl font-bold mt-12 mb-6;
}
.academic-article h3 {
@apply text-3xl font-semibold mt-8 mb-4;
}
.academic-article p {
@apply text-[21px] leading-[1.58] mb-6 text-gray-800;
}
.academic-article a {
@apply text-blue-600 hover:underline;
}
.blog-post {
@apply max-w-4xl mx-auto px-6 py-12;
}
.blog-post h2 {
@apply text-4xl font-bold mt-12 mb-6;
}
.blog-post h3 {
@apply text-3xl font-semibold mt-8 mb-4;
}
.blog-post p {
@apply text-[18px] leading-[1.75] mb-6 text-gray-700;
}
.presentation-slides {
@apply snap-y snap-mandatory h-screen overflow-y-scroll;
}
.presentation-slide {
@apply snap-start min-h-screen w-full flex items-center justify-center p-12;
}
.prose pre {
@apply bg-gray-900 text-gray-100 rounded-lg p-6 overflow-x-auto;
}
.prose code {
@apply bg-gray-100 text-gray-900 rounded px-2 py-1 text-sm;
}
.prose table {
@apply w-full border-collapse my-8;
}
.prose th {
@apply bg-gray-100 font-semibold p-3 text-left border-b-2 border-gray-300;
}
.prose td {
@apply p-3 border-b border-gray-200;
}
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
Adding Math Support
For LaTeX equations (academic articles):
The quadratic formula is:
$$
x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}
$$
Inline math: $E = mc^2$
Adding Animations
For presentations with progressive disclosure:
import { motion } from 'framer-motion'
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
Content appears smoothly
</motion.div>
Dark Mode Support
Add dark mode to any layout:
import { ThemeProvider } from 'next-themes'
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<ThemeProvider attribute="class" defaultTheme="system">
{children}
</ThemeProvider>
)
}
Custom MDX Components
Map HTML elements to custom components:
import type { MDXComponents } from 'mdx/types'
import { MarginNote } from './components/academic/MarginNote'
export function useMDXComponents(components: MDXComponents): MDXComponents {
return {
...components,
aside: MarginNote,
}
}
For SEO and social sharing:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Article Title | Your Site',
description: 'Article description for search engines',
authors: [{ name: 'Your Name' }],
openGraph: {
title: 'Article Title',
description: 'Article description',
type: 'article',
publishedTime: '2024-10-01T00:00:00.000Z',
},
}
Build and Deploy
npm run build
npm start
vercel --prod
Environment Variables
NEXT_PUBLIC_SITE_URL=https://yourdomain.com
See references/troubleshooting.md for solutions to common issues.
See references/quick-reference.md for copy-paste code snippets.
Complete working examples are available in the assets/ directory:
assets/academic/ - Full academic article example
assets/blog/ - Full blog post example
assets/presentation/ - Full presentation example
Version: 1.0.0
Last Updated: October 2024
Compatible With: Next.js 15+, React 19, TypeScript 5+