After Stripe checkout and webhooks, you need to gate features by plan. Here is the pattern.
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
import { redirect } from "next/navigation";
async function requirePro() {
const session = await auth();
if (!session?.user) redirect("/login");
const sub = await db.subscription.findUnique({ where: { userId: session.user.id } });
const isPro = sub?.status === "ACTIVE" && (sub.plan === "PRO" || sub.plan === "ENTERPRISE");
if (!isPro) redirect("/billing");
return sub;
}
Call at the top of any server component. Non-Pro users get redirected to billing.
Usage-based limits
async function checkAILimit(userId: string) {
const sub = await db.subscription.findUnique({ where: { userId } });
const limits: Record<string, number> = { FREE: 50, PRO: 1000, ENTERPRISE: -1 };
const limit = limits[sub?.plan ?? "FREE"];
if (limit === -1) return true;
const startOfMonth = new Date();
startOfMonth.setDate(1); startOfMonth.setHours(0,0,0,0);
const count = await db.message.count({
where: { conversation: { userId }, role: "USER", createdAt: { gte: startOfMonth } },
});
return count < limit;
}
In an API route
const canChat = await checkAILimit(session.user.id);
if (!canChat) return NextResponse.json({ error: "Limit reached" }, { status: 429 });
Enum-based plans + count query = scales from MVP to thousands of users.
Full implementation: LaunchKit ($49)
Top comments (0)