How to Add Stripe to a Next.js 15 App Router Project
Server Actions, Route Handlers, and the runtime gotcha that breaks most first attempts.
Zeeshan
Founder · Published
Server Actions, Route Handlers, and the runtime gotcha that breaks most first attempts.
Zeeshan
Founder · Published
App Router changes the shape of a Stripe integration more than most teams expect. The old "API routes" mental model still mostly works, but two things trip up almost every first attempt: Server Actions for creating sessions, and the runtime configuration on your webhook handler.
A Server Action is the cleanest way to create a Checkout Session from a form submission — no separate API route needed for the simple case.
"use server";
export async function createCheckoutSession() {
const session = await stripe.checkout.sessions.create({
mode: "payment",
line_items: [{ price: "price_123", quantity: 1 }],
success_url: `${process.env.SITE_URL}/success`,
cancel_url: `${process.env.SITE_URL}/cancel`,
});
redirect(session.url!);
}Webhook signature verification needs the raw request body and Node.js APIs — it will not work on the Edge runtime. Explicitly force the Node runtime on your webhook route.
export const runtime = "nodejs";
export async function POST(req: Request) {
const body = await req.text();
const sig = req.headers.get("stripe-signature")!;
const event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
// handle event
}Miss this and signature verification fails intermittently in production while working fine locally — a classic case of an environment difference masking the real bug.
Stripe insights, monthly
One email a month, no spam, unsubscribe anytime.
No spam, unsubscribe anytime.
Shopify Payments covers standard storefronts well — here's where it stops being enough.
When to reach for Cashier, and when to go straight to the Stripe PHP SDK instead.
Every support ticket a customer can resolve themselves through a portal is a ticket your team never has to touch.