diff --git a/content/blog/premium-fumadocs.mdx b/content/blog/premium-fumadocs.mdx
new file mode 100644
index 0000000..45c8d3a
--- /dev/null
+++ b/content/blog/premium-fumadocs.mdx
@@ -0,0 +1,67 @@
+---
+title: "Premium: What is Fumadocs"
+description: "Introducing Fumadocs, a docs framework that you can break."
+date: "2025-08-30"
+published: true
+premium: true
+categories: ["development", "nextjs"]
+author: "fox"
+image: "/images/blog/post-1.png"
+---
+
+Fumadocs was created because I wanted a more customisable experience for building docs, to be a docs framework that is not opinionated, **a "framework" that you can break**.
+
+## Philosophy
+
+**Less Abstraction:** Fumadocs expects you to write code and cooperate with the rest of your software.
+While most frameworks are configured with a configuration file, they usually lack flexibility when you hope to tune its details.
+You can't control how they render the page nor the internal logic. Fumadocs shows you how the app works, instead of a single configuration file.
+
+**Next.js Fundamentals:** It gives you the utilities and a good-looking UI.
+You are still using features of Next.js App Router, like **Static Site Generation**. There is nothing new for Next.js developers, so you can use it with confidence.
+
+
+
+**Opinionated on UI:** The only thing Fumadocs UI (the default theme) offers is **User Interface**. The UI is opinionated for bringing better mobile responsiveness and user experience.
+Instead, we use a much more flexible approach inspired by Shadcn UI — [Fumadocs CLI](/docs/cli), so we can iterate our design quick, and welcome for more feedback about the UI.
+
+## Why Fumadocs
+
+Fumadocs is designed with flexibility in mind.
+
+You can use `fumadocs-core` as a headless UI library and bring your own styles.
+Fumadocs MDX is also a useful library to handle MDX content in Next.js. It also includes:
+
+- Many built-in components.
+- Typescript Twoslash, OpenAPI, and Math (KaTeX) integrations.
+- Fast and optimized by default, natively built on App Router.
+- Tight integration with Next.js, you can add it to an existing Next.js project easily.
+
+You can read [Comparisons](/docs/comparisons) if you're interested.
+
+### Documentation
+
+Fumadocs focuses on **authoring experience**, it provides a beautiful theme and many docs automation tools.
+
+It helps you to iterate your codebase faster while never leaving your docs behind.
+You can take this site as an example of docs site built with Fumadocs.
+
+### Blog sites
+
+Since Next.js is already a powerful framework, most features can be implemented with **just Next.js**.
+
+Fumadocs provides additional tooling for Next.js, including syntax highlighting, document search, and a default theme (Fumadocs UI).
+It helps you to avoid reinventing the wheels.
+
+## When to use Fumadocs
+
+For most of the web applications, vanilla React.js is no longer enough.
+Nowadays, we also wish to have a blog, a showcase page, a FAQ page, etc. With a
+fancy UI that's breathtaking, in these cases, Fumadocs can help you build the
+docs easier, with less boilerplate.
+
+Fumadocs is maintained by Fuma and many contributors, with care on the maintainability of codebase.
+While we don't aim to offer every functionality people wanted, we're more focused on making basic features perfect and well-maintained.
+You can also help Fumadocs to be more useful by contributing!
+
+
diff --git a/messages/en.json b/messages/en.json
index cbd0f4d..9399fc2 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -281,7 +281,20 @@
"all": "All",
"noPostsFound": "No posts found",
"allPosts": "All Posts",
- "morePosts": "More Posts"
+ "morePosts": "More Posts",
+ "premiumContent": {
+ "title": "Unlock Premium Content",
+ "description": "Subscribe to our Pro plan to access all premium articles and exclusive content.",
+ "upgradeCta": "Upgrade Now",
+ "benefit1": "All premium articles",
+ "benefit2": "Exclusive content",
+ "benefit3": "Cancel anytime",
+ "signIn": "Sign In",
+ "loginRequired": "Sign in to continue reading",
+ "loginDescription": "This is a premium article. Sign in to your account to access the full content.",
+ "checkingAccess": "Checking access...",
+ "loadingContent": "Loading full content..."
+ }
},
"DocsPage": {
"toc": "Table of Contents",
diff --git a/source.config.ts b/source.config.ts
index 7917344..b2feabf 100644
--- a/source.config.ts
+++ b/source.config.ts
@@ -94,6 +94,7 @@ export const blog = defineCollections({
image: z.string(),
date: z.string().date(),
published: z.boolean().default(true),
+ premium: z.boolean().optional(),
categories: z.array(z.string()),
author: z.string(),
}),
diff --git a/src/components/blog/blog-card.tsx b/src/components/blog/blog-card.tsx
index 7d587c9..23eff59 100644
--- a/src/components/blog/blog-card.tsx
+++ b/src/components/blog/blog-card.tsx
@@ -4,6 +4,7 @@ import { formatDate } from '@/lib/formatter';
import { type BlogType, authorSource, categorySource } from '@/lib/source';
import Image from 'next/image';
import BlogImage from './blog-image';
+import { PremiumBadge } from './premium-badge';
interface BlogCardProps {
locale: string;
@@ -30,6 +31,13 @@ export default function BlogCard({ locale, post }: BlogCardProps) {
title={title || 'image for blog post'}
/>
+ {/* Premium badge - top right */}
+ {post.data.premium && (
+
diff --git a/src/components/blog/premium-badge.tsx b/src/components/blog/premium-badge.tsx
new file mode 100644
index 0000000..51772df
--- /dev/null
+++ b/src/components/blog/premium-badge.tsx
@@ -0,0 +1,45 @@
+'use client';
+
+import { Badge } from '@/components/ui/badge';
+import { cn } from '@/lib/utils';
+import { CrownIcon } from 'lucide-react';
+
+interface PremiumBadgeProps {
+ className?: string;
+ variant?: 'default' | 'outline' | 'secondary';
+ size?: 'sm' | 'default' | 'lg';
+}
+
+export function PremiumBadge({
+ className,
+ variant = 'default',
+ size = 'default',
+}: PremiumBadgeProps) {
+ const sizeClasses = {
+ sm: 'text-xs h-5',
+ default: 'text-xs h-6',
+ lg: 'text-sm h-7',
+ };
+
+ const iconSizes = {
+ sm: 'size-3',
+ default: 'size-3',
+ lg: 'size-4',
+ };
+
+ return (
+
+
+ Premium
+
+ );
+}
diff --git a/src/components/blog/premium-content.tsx b/src/components/blog/premium-content.tsx
new file mode 100644
index 0000000..e3b9ca8
--- /dev/null
+++ b/src/components/blog/premium-content.tsx
@@ -0,0 +1,18 @@
+'use client';
+
+import { useCurrentUser } from '@/hooks/use-current-user';
+import { useCurrentPlan } from '@/hooks/use-payment';
+import type { ReactNode } from 'react';
+
+interface PremiumContentProps {
+ children: ReactNode;
+}
+
+/**
+ * This component will now rely on server-side filtering
+ * The tags will be removed server-side for non-premium users
+ * This component only serves as a marker for premium sections
+ */
+export function PremiumContent({ children }: PremiumContentProps) {
+ return
{children}
;
+}
diff --git a/src/components/blog/premium-guard.tsx b/src/components/blog/premium-guard.tsx
new file mode 100644
index 0000000..f9f6109
--- /dev/null
+++ b/src/components/blog/premium-guard.tsx
@@ -0,0 +1,230 @@
+'use client';
+
+import { LoginWrapper } from '@/components/auth/login-wrapper';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent } from '@/components/ui/card';
+import { useCurrentUser } from '@/hooks/use-current-user';
+import { useCurrentPlan } from '@/hooks/use-payment';
+import { LocaleLink, useLocalePathname } from '@/i18n/navigation';
+import {
+ ArrowRightIcon,
+ CheckCircleIcon,
+ CrownIcon,
+ Loader2Icon,
+ LockIcon,
+} from 'lucide-react';
+import { useTranslations } from 'next-intl';
+import type { ReactNode } from 'react';
+
+interface PremiumGuardProps {
+ children: ReactNode;
+ isPremium: boolean;
+ canAccess?: boolean;
+ className?: string;
+}
+
+export function PremiumGuard({
+ children,
+ isPremium,
+ canAccess,
+ className,
+}: PremiumGuardProps) {
+ // For non-premium articles, show content immediately with no extra processing
+ if (!isPremium) {
+ return (
+
+
+ {children}
+
+
+ );
+ }
+
+ const t = useTranslations('BlogPage');
+ const pathname = useLocalePathname();
+
+ // If server-side check has already determined access, use that
+ if (canAccess !== undefined) {
+ // Server has determined the user has access
+ if (canAccess) {
+ return (
+
+
+ {children}
+
+
+ );
+ }
+
+ // Server determined no access, show appropriate message
+ const currentUser = useCurrentUser();
+ if (!currentUser) {
+ return (
+
+
+ {/* Show partial content before protection */}
+ {children}
+