Modern Blog

A clean, minimal blog template with beautiful typography, article layouts, categories, and newsletter signup. Perfect for writers and content creators.

Article LayoutsCategory PagesNewsletterCommentsAuthor Profiles

Overview

Content That Shines

A blog template designed with readability in mind. Beautiful typography, optimized reading experience, and all the features you need to grow your audience and engage readers.

📝

Beautiful Typography

Optimized line height, spacing, and font choices for reading

🏷️

Categories & Tags

Organize content with flexible taxonomy system

📧

Newsletter Integration

Built-in signup forms to grow your email list

💬

Comments System

Engage readers with threaded discussions

Blox Used

Key Features

  • • SEO optimized structure
  • • RSS feed generation
  • • Social sharing buttons
  • • Reading time estimates
  • • Related posts suggestions

Live Demo

theblog
Featured

The Future of Web Development in 2024

Exploring emerging trends and technologies shaping the future of web development.

SC
Sarah Chen·8 min read

Latest Posts

Design

Designing for Accessibility

Best practices for creating inclusive digital experiences for everyone.

MP
Michael Park
Tutorial

Building Scalable React Applications

Architecture patterns and strategies for large-scale React projects.

ED
Emily Davis
Development

The Art of Writing Clean Code

Principles and practices for maintainable, readable code.

AK
Alex Kim

Subscribe to our newsletter

Get the latest posts delivered straight to your inbox.

theblog

© 2024 All rights reserved.

Implementation Guide

1

Define Content Types

Set up types for posts, authors, and categories.

step-1.tsx
ReactTailwind
TSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// types/blog.ts export interface Post { id: string; slug: string; title: string; excerpt: string; content: string; coverImage: string; publishedAt: Date; updatedAt?: Date; author: Author; category: Category; tags: string[]; readingTime: number; featured?: boolean; } export interface Author { id: string; name: string; bio: string; avatar: string; twitter?: string; website?: string; } export interface Category { id: string; name: string; slug: string; description?: string; color: string; } export interface Comment { id: string; postId: string; author: { name: string; email: string; avatar?: string; }; content: string; createdAt: Date; parentId?: string; replies?: Comment[]; }
2

Create Article Layout

Build a beautiful, readable article page with proper typography.

step-2.tsx
ReactTailwind
TSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// components/ArticleLayout.tsx function ArticleLayout({ post }: { post: Post }) { return ( <article className="max-w-3xl mx-auto px-6 py-12"> {/* Category & Reading Time */} <div className="flex items-center gap-4 mb-6"> <Link href={`/category/${post.category.slug}`} className="px-3 py-1 text-sm font-medium bg-slate-100 dark:bg-slate-800 rounded-full hover:bg-slate-200" > {post.category.name} </Link> <span className="text-sm text-slate-500">{post.readingTime} min read</span> </div> {/* Title */} <h1 className="text-4xl md:text-5xl font-bold text-slate-900 dark:text-white leading-tight mb-6"> {post.title} </h1> {/* Author & Date */} <div className="flex items-center gap-4 mb-8"> <img src={post.author.avatar} alt={post.author.name} className="w-12 h-12 rounded-full" /> <div> <Link href={`/author/${post.author.id}`} className="font-medium hover:underline"> {post.author.name} </Link> <p className="text-sm text-slate-500"> {new Date(post.publishedAt).toLocaleDateString("en-US", { month: "long", day: "numeric", year: "numeric", })} </p> </div> </div> {/* Cover Image */} <div className="aspect-[2/1] rounded-2xl overflow-hidden mb-12"> <img src={post.coverImage} alt={post.title} className="w-full h-full object-cover" /> </div> {/* Content */} <div className="prose prose-lg dark:prose-invert prose-slate max-w-none"> <MDXContent content={post.content} /> </div> {/* Tags */} <div className="flex flex-wrap gap-2 mt-12 pt-8 border-t border-slate-200 dark:border-slate-800"> {post.tags.map(tag => ( <Link key={tag} href={`/tag/${tag}`} className="px-3 py-1 text-sm text-slate-600 dark:text-slate-400 bg-slate-100 dark:bg-slate-800 rounded-full hover:bg-slate-200" > #{tag} </Link> ))} </div> {/* Author Bio */} <AuthorCard author={post.author} /> {/* Comments Section */} <CommentsSection postId={post.id} /> </article> ); }
3

Build Post Grid

Create a responsive grid of post cards with featured posts.

step-3.tsx
ReactTailwind
TSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
function PostGrid({ posts, featured }: { posts: Post[]; featured?: Post }) { return ( <div className="space-y-12"> {/* Featured Post */} {featured && ( <Link href={`/blog/${featured.slug}`} className="group block"> <div className="grid md:grid-cols-2 gap-8 items-center"> <div className="aspect-[4/3] rounded-2xl overflow-hidden"> <img src={featured.coverImage} alt={featured.title} className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" /> </div> <div> <span className="text-sm font-medium text-slate-500">Featured</span> <h2 className="text-3xl font-bold text-slate-900 dark:text-white mt-2 mb-4 group-hover:text-slate-700 dark:group-hover:text-slate-300"> {featured.title} </h2> <p className="text-slate-600 dark:text-slate-400 mb-4 line-clamp-3"> {featured.excerpt} </p> <div className="flex items-center gap-3"> <img src={featured.author.avatar} alt={featured.author.name} className="w-8 h-8 rounded-full" /> <span className="text-sm font-medium">{featured.author.name}</span> <span className="text-sm text-slate-500">·</span> <span className="text-sm text-slate-500">{featured.readingTime} min read</span> </div> </div> </div> </Link> )} {/* Post Grid */} <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8"> {posts.map(post => ( <Link key={post.id} href={`/blog/${post.slug}`} className="group"> <div className="aspect-[3/2] rounded-xl overflow-hidden mb-4"> <img src={post.coverImage} alt={post.title} className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" /> </div> <span className="text-sm font-medium text-slate-500">{post.category.name}</span> <h3 className="text-xl font-semibold text-slate-900 dark:text-white mt-1 mb-2 group-hover:text-slate-700 dark:group-hover:text-slate-300 line-clamp-2"> {post.title} </h3> <p className="text-slate-600 dark:text-slate-400 text-sm line-clamp-2"> {post.excerpt} </p> <div className="flex items-center gap-2 mt-4"> <img src={post.author.avatar} alt={post.author.name} className="w-6 h-6 rounded-full" /> <span className="text-sm text-slate-600 dark:text-slate-400"> {post.author.name} </span> </div> </Link> ))} </div> </div> ); }
4

Add Newsletter Signup

Create a newsletter subscription form with validation.

step-4.tsx
ReactTailwind
TSX
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
function NewsletterSignup() { const [email, setEmail] = useState(""); const [status, setStatus] = useState<"idle" | "loading" | "success" | "error">("idle"); const handleSubmit = async (e: FormEvent) => { e.preventDefault(); setStatus("loading"); try { await subscribeToNewsletter(email); setStatus("success"); setEmail(""); } catch { setStatus("error"); } }; if (status === "success") { return ( <div className="bg-slate-100 dark:bg-slate-800 rounded-2xl p-8 text-center"> <div className="w-12 h-12 bg-emerald-100 rounded-full flex items-center justify-center mx-auto mb-4"> <svg className="w-6 h-6 text-emerald-600" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /> </svg> </div> <h3 className="text-xl font-semibold mb-2">You're subscribed!</h3> <p className="text-slate-600 dark:text-slate-400"> Thanks for subscribing. Check your email for confirmation. </p> </div> ); } return ( <div className="bg-slate-100 dark:bg-slate-800 rounded-2xl p-8"> <h3 className="text-2xl font-bold text-slate-900 dark:text-white mb-2"> Subscribe to our newsletter </h3> <p className="text-slate-600 dark:text-slate-400 mb-6"> Get the latest posts delivered straight to your inbox. </p> <form onSubmit={handleSubmit} className="flex gap-3"> <input type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="Enter your email" required className="flex-1 px-4 py-3 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-500" /> <button type="submit" disabled={status === "loading"} className="px-6 py-3 bg-slate-900 dark:bg-white text-white dark:text-slate-900 font-medium rounded-xl hover:bg-slate-800 dark:hover:bg-slate-100 disabled:opacity-50" > {status === "loading" ? "..." : "Subscribe"} </button> </form> {status === "error" && ( <p className="mt-3 text-sm text-red-600"> Something went wrong. Please try again. </p> )} </div> ); }

Full Source Code

blog.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
"use client"; import { useState } from "react"; import Link from "next/link"; // Types interface Post { id: string; slug: string; title: string; excerpt: string; coverImage: string; category: string; author: { name: string; avatar: string; }; readingTime: number; publishedAt: Date; } // Blog Home Page export default function BlogHome() { const [posts] = useState<Post[]>([]); return ( <div className="min-h-screen bg-white dark:bg-slate-950"> <Header /> <main className="max-w-5xl mx-auto px-6 py-12"> <FeaturedPost post={posts[0]} /> <PostGrid posts={posts.slice(1)} /> <NewsletterSignup /> </main> <Footer /> </div> ); } // Header Component function Header() { return ( <header className="border-b border-slate-200 dark:border-slate-800"> <div className="max-w-5xl mx-auto px-6 h-16 flex items-center justify-between"> <Link href="/" className="text-xl font-bold text-slate-900 dark:text-white"> theblog </Link> <nav className="hidden md:flex items-center gap-8"> {["Articles", "Categories", "About", "Newsletter"].map((item) => ( <Link key={item} href={`/${item.toLowerCase()}`} className="text-sm font-medium text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-white transition-colors" > {item} </Link> ))} </nav> <button className="p-2 hover:bg-slate-100 dark:hover:bg-slate-800 rounded-lg"> <SearchIcon className="w-5 h-5 text-slate-600" /> </button> </div> </header> ); } // Featured Post Component function FeaturedPost({ post }: { post?: Post }) { if (!post) return null; return ( <Link href={`/blog/${post.slug}`} className="group block mb-16"> <div className="grid md:grid-cols-2 gap-8 items-center"> <div className="aspect-[4/3] rounded-2xl overflow-hidden"> <img src={post.coverImage} alt={post.title} className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" /> </div> <div> <span className="text-sm font-medium text-slate-500">Featured</span> <h1 className="text-3xl font-bold text-slate-900 dark:text-white mt-2 mb-4 group-hover:text-slate-700 dark:group-hover:text-slate-300 transition-colors"> {post.title} </h1> <p className="text-slate-600 dark:text-slate-400 mb-4 line-clamp-3"> {post.excerpt} </p> <AuthorInfo author={post.author} readingTime={post.readingTime} /> </div> </div> </Link> ); } // Post Grid Component function PostGrid({ posts }: { posts: Post[] }) { return ( <section className="mb-16"> <h2 className="text-2xl font-bold text-slate-900 dark:text-white mb-8">Latest Posts</h2> <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-8"> {posts.map(post => ( <Link key={post.id} href={`/blog/${post.slug}`} className="group"> <div className="aspect-[3/2] rounded-xl overflow-hidden mb-4"> <img src={post.coverImage} alt={post.title} className="w-full h-full object-cover group-hover:scale-105 transition-transform duration-500" /> </div> <span className="text-sm font-medium text-slate-500">{post.category}</span> <h3 className="text-xl font-semibold text-slate-900 dark:text-white mt-1 mb-2 group-hover:text-slate-700 dark:group-hover:text-slate-300 line-clamp-2 transition-colors"> {post.title} </h3> <p className="text-slate-600 dark:text-slate-400 text-sm line-clamp-2"> {post.excerpt} </p> <AuthorInfo author={post.author} readingTime={post.readingTime} size="sm" /> </Link> ))} </div> </section> ); } // Newsletter Signup Component function NewsletterSignup() { const [email, setEmail] = useState(""); const [status, setStatus] = useState<"idle" | "loading" | "success">("idle"); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setStatus("loading"); // Simulate API call await new Promise(r => setTimeout(r, 1000)); setStatus("success"); }; if (status === "success") { return ( <div className="bg-slate-100 dark:bg-slate-800 rounded-2xl p-8 text-center"> <div className="w-12 h-12 bg-emerald-100 rounded-full flex items-center justify-center mx-auto mb-4"> <CheckIcon className="w-6 h-6 text-emerald-600" /> </div> <h3 className="text-xl font-semibold mb-2">You're subscribed!</h3> <p className="text-slate-600 dark:text-slate-400"> Thanks for subscribing. Check your email for confirmation. </p> </div> ); } return ( <div className="bg-slate-100 dark:bg-slate-800 rounded-2xl p-8"> <div className="max-w-xl mx-auto text-center"> <h3 className="text-2xl font-bold text-slate-900 dark:text-white mb-2"> Subscribe to our newsletter </h3> <p className="text-slate-600 dark:text-slate-400 mb-6"> Get the latest posts delivered straight to your inbox. </p> <form onSubmit={handleSubmit} className="flex gap-3"> <input type="email" value={email} onChange={e => setEmail(e.target.value)} placeholder="Enter your email" required className="flex-1 px-4 py-3 bg-white dark:bg-slate-900 rounded-xl border border-slate-200 dark:border-slate-700 focus:outline-none focus:ring-2 focus:ring-slate-500" /> <button type="submit" disabled={status === "loading"} className="px-6 py-3 bg-slate-900 dark:bg-white text-white dark:text-slate-900 font-medium rounded-xl hover:bg-slate-800 disabled:opacity-50" > {status === "loading" ? "..." : "Subscribe"} </button> </form> </div> </div> ); } // Helper Components function AuthorInfo({ author, readingTime, size = "md" }: { author: { name: string; avatar: string }; readingTime: number; size?: "sm" | "md"; }) { return ( <div className={`flex items-center gap-${size === "sm" ? "2" : "3"} mt-4`}> <img src={author.avatar} alt={author.name} className={`rounded-full ${size === "sm" ? "w-6 h-6" : "w-8 h-8"}`} /> <span className={`font-medium ${size === "sm" ? "text-sm" : ""}`}>{author.name}</span> <span className="text-slate-500">·</span> <span className={`text-slate-500 ${size === "sm" ? "text-sm" : ""}`}>{readingTime} min read</span> </div> ); } function Footer() { return ( <footer className="border-t border-slate-200 dark:border-slate-800 mt-16"> <div className="max-w-5xl mx-auto px-6 py-12"> <div className="flex flex-col md:flex-row justify-between items-center gap-6"> <span className="text-xl font-bold">theblog</span> <div className="flex gap-6"> {["Twitter", "GitHub", "RSS"].map(social => ( <a key={social} href="#" className="text-sm text-slate-500 hover:text-slate-900 dark:hover:text-white"> {social} </a> ))} </div> <p className="text-sm text-slate-500">© 2024 All rights reserved.</p> </div> </div> </footer> ); } function SearchIcon({ className }: { className?: string }) { return ( <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> </svg> ); } function CheckIcon({ className }: { className?: string }) { return ( <svg className={className} fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /> </svg> ); }

Customization Guide

Typography Settings

Customize fonts and reading experience.

customization.tsx
ReactTailwind
TSX
// tailwind.config.ts
module.exports = {
  theme: {
    extend: {
      typography: {
        DEFAULT: {
          css: {
            maxWidth: '65ch',
            lineHeight: '1.75',
            'h1, h2, h3': {
              fontWeight: '700',
              letterSpacing: '-0.025em',
            },
            a: {
              color: '#3b82f6',
              textDecoration: 'underline',
              '&:hover': {
                color: '#2563eb',
              },
            },
          },
        },
      },
    },
  },
  plugins: [require('@tailwindcss/typography')],
};

Category Colors

Define colors for different content categories.

customization.tsx
ReactTailwind
TSX
const categoryColors: Record<string, string> = {
  technology: "bg-blue-100 text-blue-700",
  design: "bg-pink-100 text-pink-700",
  business: "bg-emerald-100 text-emerald-700",
  lifestyle: "bg-purple-100 text-purple-700",
  tutorial: "bg-amber-100 text-amber-700",
};

// Usage
<span className={`px-3 py-1 text-sm rounded-full ${categoryColors[category]}`}>
  {category}
</span>

Social Sharing

Add social sharing buttons to posts.

customization.tsx
ReactTailwind
TSX
const socialLinks = [
  {
    name: "Twitter",
    getUrl: (post: Post) =>
      `https://twitter.com/intent/tweet?text=${encodeURIComponent(post.title)}&url=${encodeURIComponent(window.location.href)}`,
    icon: TwitterIcon,
  },
  {
    name: "LinkedIn",
    getUrl: (post: Post) =>
      `https://linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(window.location.href)}&title=${encodeURIComponent(post.title)}`,
    icon: LinkedInIcon,
  },
  {
    name: "Copy Link",
    onClick: () => navigator.clipboard.writeText(window.location.href),
    icon: LinkIcon,
  },
];

RSS Feed

Generate an RSS feed for your blog.

customization.tsx
ReactTailwind
TSX
// app/feed.xml/route.ts
import { getPosts } from "@/lib/posts";

export async function GET() {
  const posts = await getPosts();

  const feed = `<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>My Blog</title>
    <link>https://myblog.com</link>
    <description>My blog description</description>
    ${posts.map(post => `
    <item>
      <title>${post.title}</title>
      <link>https://myblog.com/blog/${post.slug}</link>
      <description>${post.excerpt}</description>
      <pubDate>${new Date(post.publishedAt).toUTCString()}</pubDate>
    </item>
    `).join("")}
  </channel>
</rss>`;

  return new Response(feed, {
    headers: {
      "Content-Type": "application/xml",
    },
  });
}

Ready to Start Writing?

Copy this template and start sharing your stories. Focus on content while we handle the design.