Stripe Integration in Next.js: Subscription Billing and Payment Processing

Stripe remains the gold standard for payment processing on the web, and its integration with Next.js has become more seamless with each framework release. Whether you're building a SaaS with recurring subscriptions or an e-commerce storefront with one-time purchases, the combination of Stripe's APIs and Next.js's App Router provides a robust foundation for handling money on the internet.
Setting Up Stripe in Next.js
Start by installing the Stripe SDK and its TypeScript types. The server-side client should be initialized in a shared module with your secret key, while the client-side @stripe/react-stripe-js package handles tokenized card entry:
// lib/stripe-server.ts
import Stripe from 'stripe'
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2025-02-24', // latest stable
typescript: true,
})
// app/providers/stripe-provider.tsx
'use client'
import { Elements } from '@stripe/react-stripe-js'
import { loadStripe } from '@stripe/stripe-js'
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!)
export function StripeProvider({ children }: { children: React.ReactNode }) {
return (
<Elements stripe={stripePromise}>
{children}
</Elements>
)
}
Never expose the secret key to the client. Use Server Components or Route Handlers for all Stripe API calls that require authentication.
Creating Checkout Sessions
The recommended approach for accepting payments is Stripe Checkout—a hosted payment page that handles card entry, address collection, and payment method storage:
// app/api/checkout/route.ts
import { stripe } from '@/lib/stripe-server'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(req: NextRequest) {
const { priceId, userId } = await req.json()
const session = await stripe.checkout.sessions.create({
customer_email: user.email,
line_items: [{ price: priceId, quantity: 1 }],
mode: 'subscription',
success_url: `${req.headers.get('origin')}/dashboard?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${req.headers.get('origin')}/pricing`,
metadata: { userId },
})
return NextResponse.json({ url: session.url })
}
Redirect the user to session.url and Stripe handles the rest. After payment, the customer is redirected back to your success URL with the session ID as a query parameter.
Handling Webhooks for Subscription Lifecycle
Webhooks are critical for subscription management. Stripe sends events when invoices are paid, subscriptions renew, or payments fail. Use Next.js Route Handlers with raw body parsing (required for signature verification):
// app/api/webhooks/stripe/route.ts
import { stripe } from '@/lib/stripe-server'
import { NextRequest, NextResponse } from 'next/server'
export async function POST(req: NextRequest) {
const body = await req.text()
const sig = req.headers.get('stripe-signature')!
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(
body, sig, process.env.STRIPE_WEBHOOK_SECRET!
)
} catch {
return NextResponse.json({ error: 'Invalid signature' }, { status: 400 })
}
switch (event.type) {
case 'checkout.session.completed': {
const session = event.data.object
await activateUserSubscription(session.metadata.userId, session.subscription)
break
}
case 'invoice.payment_failed': {
const invoice = event.data.object
await notifyPaymentFailure(invoice.subscription)
break
}
case 'customer.subscription.deleted': {
const subscription = event.data.object
await deactivateSubscription(subscription.id)
break
}
}
return NextResponse.json({ received: true })
}
Test webhooks locally using the Stripe CLI, which forwards events to your local server with stripe listen --forward-to localhost:3000/api/webhooks/stripe.
Customer Portal for Self-Service
Stripe's Customer Portal allows users to manage their subscription without exposing your admin interface. Generate a portal session link and redirect users there:
export async function createPortalSession(customerId: string) {
const session = await stripe.billingPortal.sessions.create({
customer: customerId,
return_url: `${process.env.NEXT_PUBLIC_BASE_URL}/dashboard`,
})
return session.url
}
The portal handles plan upgrades, downgrades, payment method updates, and cancellations. It saves months of development time compared to building these UIs yourself.
Error Handling and Resilience
Payment failures happen. Stripe's API returns specific error types that map to user-facing messages:
try {
await stripe.paymentIntents.create({ ... })
} catch (error) {
if (error instanceof Stripe.errors.StripeCardError) {
return { error: 'Your card was declined. Please try a different payment method.' }
}
if (error instanceof Stripe.errors.RateLimitError) {
return { error: 'Too many requests. Please try again.' }
}
// Log to error tracking
return { error: 'An unexpected error occurred.' }
}
For recurring billing, implement dunning—automated retry logic that Stripe provides out of the box. Configure Smart Retries in the Stripe dashboard to retry failed payments on a schedule that maximizes success rates.
Stripe and Next.js together give you a production-ready payment infrastructure in a single codebase. From checkout sessions to recurring billing to webhook-driven fulfillment, the patterns above form the backbone of any serious web application that handles money.
<a href="/services/web-development">Our web development team</a> has deep experience with Stripe integrations and can accelerate your go-to-market timeline. Let's talk about your payment requirements.
Related Insights

CI/CD Pipeline for Next.js: GitHub Actions to Vercel and Docker Deployments
A step-by-step guide to building CI/CD pipelines for Next.js applications using GitHub Actions including automated testing, preview deployments, Docker builds, and production rollouts.

Edge Computing with Next.js: Deploying to the Network Edge for Speed
Learn how to deploy Next.js applications to the edge using Vercel Edge Functions, Cloudflare Workers, and edge-rendered pages for sub-50ms response times.

Gatsby vs Next.js: Choosing Your React Framework in 2026
A comparison of Gatsby vs Next.js for building React websites covering performance, data fetching, plugin ecosystem, hosting options, and use case suitability.