Stripe Connect in Next.js
Aug 28th 2025
This is what you need to do:
src/components/stripe/provider.tsx
"use client";
import { useCallback, useEffect, useState } from "react";
import { createAccountSessionAction } from "@/actions";
import { ConnectComponentsProvider } from "@stripe/react-connect-js";
import {
type AppearanceOptions,
type StripeConnectInstance,
loadConnectAndInitialize,
} from "@stripe/connect-js";
export default function StripeConnectProvider({
userName,
children,
}: {
userName: string;
children: React.ReactNode;
}) {
const [stripeConnectInstance, setStripeConnectInstance] =
useState<StripeConnectInstance | null>(null);
async function fetchClientSecret() {
return await createAccountSessionAction({ userName }),
}
useEffect(() => {
if (!userName || stripeConnectInstance) return;
const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
if (!publishableKey) {
throw new Error("stripe publishable key is not set");
}
setStripeConnectInstance(
loadConnectAndInitialize({
fetchClientSecret,
fonts,
publishableKey,
appearance,
})
);
}, [userName, fetchClientSecret, stripeConnectInstance]);
return (
stripeConnectInstance && (
<ConnectComponentsProvider connectInstance={stripeConnectInstance}>
{children}
</ConnectComponentsProvider>
)
);
}
Notice how I'm importing createAccountSessionAction from "@/actions".
For styling:
src/components/stripe/provider.tsx
import colors from "tailwindcss/colors";
const INTER_URL =
"https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap";
const fonts = [
{
cssSrc: INTER_URL,
},
];
const appearance: AppearanceOptions = {
variables: {
colorBackground: colors.neutral[950],
colorPrimary: colors.neutral[100],
colorText: colors.neutral[100],
colorSecondaryText: colors.neutral[500],
formBackgroundColor: colors.neutral[900],
formAccentColor: colors.neutral[300],
offsetBackgroundColor: colors.neutral[900],
overlayBackdropColor: "rgba(10, 10, 10, 0.8)",
actionSecondaryColorText: colors.neutral[700],
buttonSecondaryColorBackground: colors.neutral[900],
buttonSecondaryColorBorder: colors.neutral[700],
buttonSecondaryColorText: colors.neutral[50],
colorBorder: colors.neutral[700],
colorDanger: colors.red[600],
spacingUnit: "10px",
actionPrimaryTextDecorationLine: "underline",
headingXlFontWeight: "400",
labelMdFontWeight: "400",
bodyMdFontWeight: "400",
labelSmFontWeight: "200",
bodySmFontWeight: "400",
borderRadius: "24px",
buttonBorderRadius: "24px",
formBorderRadius: "8px",
badgeBorderRadius: "24px",
overlayBorderRadius: "24px",
badgeSuccessColorBackground: colors.emerald[950],
badgeSuccessColorText: colors.emerald[300],
badgeSuccessColorBorder: colors.emerald[950],
badgeDangerColorBackground: colors.red[950],
badgeDangerColorText: colors.red[300],
badgeWarningColorBackground: colors.amber[950],
badgeWarningColorText: colors.amber[300],
badgeWarningColorBorder: colors.amber[950],
badgeNeutralColorBackground: colors.neutral[800],
badgeNeutralColorText: colors.neutral[50],
badgeNeutralColorBorder: colors.neutral[800],
},
};
I use the Tailwind colors directly. And to change the font I get the url from google fonts. That's the only way I found that works.
The createAccountSessionAction looks like this:
src/actions.ts
const createAccountSessionActionSchema = z.object({
userName: z.string(),
});
type CreateAccountSessionActionSchema = z.infer<
typeof createAccountSessionActionSchema
>;
export async function createAccountSessionAction(
createData: CreateAccountSessionActionSchema
) {
try {
const { userId } = await auth();
if (!userId) throw new Error("User not signed in");
createAccountSessionActionSchema.parse(createData);
const user = await readUser(userId);
if (!user) throw new Error("User not found");
if (user.userName !== createData.userName) {
throw new Error("User not authorized");
}
if (user.stripeId === null) {
throw new Error("No stripe account linked");
}
const clientSecret = await createAccountSession(user.stripeId);
if (!clientSecret) throw new Error("Error creating account session");
return clientSecret;
} catch (error) {
await log.error("Error creating account session", {
error,
createData,
});
throw new Error();
}
I do some validation and create the session.
This is the createAccountSession function:
src/stripe.ts
export const stripe = new Stripe(STRIPE_SECRET_KEY);
export async function createAccountSession(account: string) {
try {
const session = await stripe.accountSessions.create({
account,
components: {
account_onboarding: {
enabled: true,
features: {
external_account_collection: true,
},
},
payments: {
enabled: true,
features: {
capture_payments: true,
refund_management: true,
dispute_management: true,
destination_on_behalf_of_charge_management: true,
},
},
account_management: { enabled: true },
notification_banner: { enabled: true },
balances: { enabled: true },
documents: { enabled: true },
tax_settings: { enabled: true },
tax_registrations: { enabled: true },
payouts: { enabled: true },
payouts_list: { enabled: true },
payment_details: {
enabled: true,
features: {
capture_payments: true,
refund_management: true,
dispute_management: true,
destination_on_behalf_of_charge_management: true,
},
},
},
});
return session.client_secret;
} catch (error) {
await log.error("Error creating account session", error);
return null;
}
}
To get the full code check my noos repo.