@pubflow/nextjs
Next.js integration for Flowfull Clients with SSR support, API route helpers, middleware, Bridge Payments, and cookie-based session management.
📚 Documentation
- Flowless (Authentication Backend): https://flowless.dev/
- Flowfull Client Libraries: https://clients.flowfull.dev/
- Bridge Payments: https://bridgepayments.dev/
Installation
npm install @pubflow/core @pubflow/nextjs swr✨ Features
- ✅ Server-Side Rendering (SSR) - Full SSR support with App Router
- ✅ API Route Helpers - Simplified API route creation
- ✅ Bridge Payments - Payment processing with Stripe, PayPal, etc.
- ✅ Authentication - Complete auth flow with Flowless
- ✅ Cookie-Based Sessions - Secure httpOnly cookies
- ✅ Middleware - Route protection and authentication
- ✅ TypeScript - Full type safety
- ✅ App Router & Pages Router - Support for both routing systems
Quick Start
1. Setup Provider (App Router)
// app/providers.tsx
'use client';
import { PubflowProvider } from '@pubflow/nextjs';
export function Providers({ children }: { children: React.ReactNode }) {
return (
<PubflowProvider url={process.env.NEXT_PUBLIC_BACKEND_URL!}>
{children}
</PubflowProvider>
);
}// app/layout.tsx
import { Providers } from './providers';
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}2. Client Component
// app/dashboard/page.tsx
'use client';
import { useAuth, useBridgeQuery } from '@pubflow/nextjs';
export default function DashboardPage() {
const { user, logout } = useAuth();
const { data, error, isLoading } = useBridgeQuery('/api/users');
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>Welcome, {user?.name}!</h1>
<button onClick={logout}>Logout</button>
<ul>
{data.map((user: any) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}3. Server Component with SSR
// app/profile/page.tsx
import { withPubflowAuthSSR } from '@pubflow/nextjs';
export default withPubflowAuthSSR(
async function ProfilePage({ user }) {
// This runs on the server
// user is automatically available
return (
<div>
<h1>Profile</h1>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
</div>
);
},
{
redirectTo: '/login', // Redirect if not authenticated
}
);4. API Route with Authentication
// app/api/users/route.ts
import { withPubflow } from '@pubflow/nextjs';
export const GET = withPubflow(async (req, { user, api }) => {
// user is automatically available if authenticated
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
// api is the authenticated API client
const users = await api.get('/api/users');
return Response.json({ users });
});
export const POST = withPubflow(async (req, { user, api }) => {
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const body = await req.json();
const newUser = await api.post('/api/users', body);
return Response.json(newUser);
});Provider
PubflowProvider
Same API as React, but with cookie-based session management.
'use client';
import { PubflowProvider } from '@pubflow/nextjs';
<PubflowProvider
url={process.env.NEXT_PUBLIC_BACKEND_URL!}
instanceId="default"
onAuthError={(error) => console.error('Auth error:', error)}
onSessionExpired={() => {
window.location.href = '/login';
}}
>
<App />
</PubflowProvider>Hooks
All hooks from @pubflow/react are available, plus Next.js-specific hooks:
useAuth()
Same as React, but with cookie-based storage.
'use client';
import { useAuth } from '@pubflow/nextjs';
export function LoginForm() {
const { login, isLoading, error } = useAuth();
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
await login({ email: 'user@example.com', password: 'password123' });
};
return <form onSubmit={handleLogin}>...</form>;
}useServerAuth()
Access server-side authentication state in client components.
'use client';
import { useServerAuth } from '@pubflow/nextjs';
export function ServerAuthExample() {
const { user, isAuthenticated, isLoading } = useServerAuth();
if (isLoading) return <div>Loading...</div>;
if (!isAuthenticated) return <div>Not authenticated</div>;
return <div>Welcome, {user?.name}!</div>;
}useBridgeQuery(), useBridgeMutation(), useBridgeCrud(), useBridgeApi()
Same as React. See @pubflow/react documentation for details.
useSearchQueryBuilder()
Build search queries for data tables.
'use client';
import { useSearchQueryBuilder } from '@pubflow/nextjs';
export function UserSearch() {
const { query, setFilter, setSort, setPage } = useSearchQueryBuilder();
return (
<div>
<input onChange={(e) => setFilter('name', e.target.value)} />
<button onClick={() => setSort('created_at', 'desc')}>Sort by Date</button>
<button onClick={() => setPage(2)}>Page 2</button>
<p>Query: {query}</p>
</div>
);
}SSR Utilities
withPubflowSSR()
Wrap server components to access Pubflow context.
import { withPubflowSSR } from '@pubflow/nextjs';
export default withPubflowSSR(async function HomePage({ api, storage }) {
// api is the API client
// storage is the server storage adapter
const data = await api.get('/api/public-data');
return <div>{JSON.stringify(data)}</div>;
});withPubflowAuthSSR()
Wrap server components that require authentication.
import { withPubflowAuthSSR } from '@pubflow/nextjs';
export default withPubflowAuthSSR(
async function ProtectedPage({ user, api }) {
// user is guaranteed to be available
// Automatically redirects if not authenticated
const userData = await api.get(`/api/users/${user.id}`);
return (
<div>
<h1>Welcome, {user.name}!</h1>
<pre>{JSON.stringify(userData, null, 2)}</pre>
</div>
);
},
{
redirectTo: '/login', // Where to redirect if not authenticated
}
);Options
interface AuthSSROptions {
redirectTo?: string; // Redirect URL if not authenticated
onUnauthorized?: (req: Request) => Response; // Custom unauthorized handler
}API Route Helpers
withPubflow()
Wrap API routes to access Pubflow context.
import { withPubflow } from '@pubflow/nextjs';
export const GET = withPubflow(async (req, { user, api, storage }) => {
// user is available if authenticated (null otherwise)
// api is the authenticated API client
// storage is the server storage adapter
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const data = await api.get('/api/data');
return Response.json(data);
});Context Object
The context object passed to wrapped functions contains:
interface PubflowContext {
user: User | null; // Current user (null if not authenticated)
api: ApiClient; // Authenticated API client
storage: ServerStorageAdapter; // Server storage adapter
sessionId: string | null; // Current session ID
}Components
All components from @pubflow/react are available:
BridgeView- Display data from APIBridgeTable- Data table with sorting/filteringBridgeForm- Form with validationBridgeList- List with infinite scrollOfflineIndicator- Connection statusAdvancedFilter- Advanced filtering
See @pubflow/react documentation for component details.
Storage
ServerStorageAdapter
Cookie-based storage for server-side rendering.
import { ServerStorageAdapter } from '@pubflow/nextjs';
import { cookies } from 'next/headers';
const storage = new ServerStorageAdapter(cookies());
// Session is stored in httpOnly cookie
await storage.setItem('pubflow_session_id', 'ses_...');Cookie Configuration
{
httpOnly: true, // Not accessible to JavaScript
secure: true, // HTTPS only in production
sameSite: 'lax', // CSRF protection
maxAge: 30 * 24 * 60 * 60, // 30 days
}ClientStorageAdapter
Browser localStorage for client-side rendering.
import { ClientStorageAdapter } from '@pubflow/nextjs';
const storage = new ClientStorageAdapter();
// Same as React's LocalStorageAdapter
await storage.setItem('pubflow_session_id', 'ses_...');Examples
Protected Dashboard (SSR)
// app/dashboard/page.tsx
import { withPubflowAuthSSR } from '@pubflow/nextjs';
export default withPubflowAuthSSR(
async function DashboardPage({ user, api }) {
// Fetch data on the server
const stats = await api.get('/api/stats');
const recentActivity = await api.get('/api/activity/recent');
return (
<div>
<h1>Dashboard</h1>
<p>Welcome back, {user.name}!</p>
<div>
<h2>Stats</h2>
<pre>{JSON.stringify(stats, null, 2)}</pre>
</div>
<div>
<h2>Recent Activity</h2>
<ul>
{recentActivity.map((item: any) => (
<li key={item.id}>{item.description}</li>
))}
</ul>
</div>
</div>
);
},
{ redirectTo: '/login' }
);Login Page
// app/login/page.tsx
'use client';
import { useAuth } from '@pubflow/nextjs';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
export default function LoginPage() {
const { login, isLoading, error } = useAuth();
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const router = useRouter();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
try {
await login({ email: email.toLowerCase(), password });
router.push('/dashboard');
} catch (err) {
console.error('Login failed:', err);
}
};
return (
<form onSubmit={handleSubmit}>
<h1>Login</h1>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value.toLowerCase())}
placeholder="Email"
required
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="Password"
required
/>
{error && <p style={{ color: 'red' }}>{error.message}</p>}
<button type="submit" disabled={isLoading}>
{isLoading ? 'Logging in...' : 'Login'}
</button>
</form>
);
}API Route with CRUD Operations
// app/api/users/route.ts
import { withPubflow } from '@pubflow/nextjs';
export const GET = withPubflow(async (req, { user, api }) => {
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const users = await api.get('/api/users');
return Response.json(users);
});
export const POST = withPubflow(async (req, { user, api }) => {
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const body = await req.json();
const newUser = await api.post('/api/users', body);
return Response.json(newUser, { status: 201 });
});// app/api/users/[id]/route.ts
import { withPubflow } from '@pubflow/nextjs';
export const GET = withPubflow(async (req, { user, api }, { params }) => {
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const userId = params.id;
const userData = await api.get(`/api/users/${userId}`);
return Response.json(userData);
});
export const PUT = withPubflow(async (req, { user, api }, { params }) => {
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const userId = params.id;
const body = await req.json();
const updatedUser = await api.put(`/api/users/${userId}`, body);
return Response.json(updatedUser);
});
export const DELETE = withPubflow(async (req, { user, api }, { params }) => {
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const userId = params.id;
await api.delete(`/api/users/${userId}`);
return Response.json({ success: true });
});Client Component with Data Fetching
// app/users/page.tsx
'use client';
import { useBridgeQuery, useBridgeMutation } from '@pubflow/nextjs';
import { useState } from 'react';
export default function UsersPage() {
const { data: users, error, isLoading, mutate } = useBridgeQuery('/api/users');
const { trigger: createUser, isMutating } = useBridgeMutation('/api/users', 'POST');
const [name, setName] = useState('');
const handleCreate = async (e: React.FormEvent) => {
e.preventDefault();
await createUser({ name, email: `${name.toLowerCase()}@example.com` });
setName('');
mutate(); // Refresh the list
};
if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
<h1>Users</h1>
<form onSubmit={handleCreate}>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Name"
/>
<button type="submit" disabled={isMutating}>
{isMutating ? 'Creating...' : 'Create User'}
</button>
</form>
<ul>
{users.map((user: any) => (
<li key={user.id}>
{user.name} - {user.email}
</li>
))}
</ul>
</div>
);
}Middleware for Authentication
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const sessionId = request.cookies.get('pubflow_session_id')?.value;
// Protect routes
if (request.nextUrl.pathname.startsWith('/dashboard')) {
if (!sessionId) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
// Redirect to dashboard if already logged in
if (request.nextUrl.pathname === '/login') {
if (sessionId) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/login'],
};Bridge Payments
The Next.js package includes full support for Bridge Payments with both server-side and client-side integration.
Server-Side Payment Processing (API Route)
// app/api/create-payment/route.ts
import { withPubflow } from '@pubflow/nextjs';
import { BridgePaymentClient } from '@pubflow/nextjs';
export const POST = withPubflow(async (req, { user }) => {
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
try {
const body = await req.json();
// Initialize Bridge Payments on server
const payments = new BridgePaymentClient({
baseUrl: process.env.BRIDGE_URL!,
getSessionId: async () => {
// Session is available from cookies
return req.cookies.get('pubflow_session_id')?.value || null;
},
});
// Create payment intent
const intent = await payments.createPaymentIntent({
total_cents: body.amount,
currency: 'USD',
description: body.description,
provider_id: 'stripe',
});
return Response.json({ intent });
} catch (error: any) {
return Response.json({ error: error.message }, { status: 500 });
}
});Client-Side Payment Integration
// app/checkout/page.tsx
'use client';
import { BridgePaymentClient } from '@pubflow/nextjs';
import { useState } from 'react';
import { loadStripe } from '@stripe/stripe-js';
import { CardElement, Elements, useStripe, useElements } from '@stripe/react-stripe-js';
const stripePromise = loadStripe(process.env.NEXT_PUBLIC_STRIPE_KEY!);
function CheckoutForm() {
const stripe = useStripe();
const elements = useElements();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!stripe || !elements) return;
setLoading(true);
setError(null);
try {
// Initialize Bridge Payments client
const payments = new BridgePaymentClient({
baseUrl: process.env.NEXT_PUBLIC_BRIDGE_URL!,
getSessionId: async () => {
// Get session from cookies (client-side)
const cookies = document.cookie.split(';');
const sessionCookie = cookies.find(c => c.trim().startsWith('pubflow_session_id='));
return sessionCookie?.split('=')[1] || null;
},
});
// Create payment intent
const intent = await payments.createPaymentIntent({
total_cents: 5000, // $50.00
currency: 'USD',
description: 'Product Purchase',
provider_id: 'stripe',
});
// Create token from card
const cardElement = elements.getElement(CardElement);
const { token, error: tokenError } = await stripe.createToken(cardElement!);
if (tokenError) {
setError(tokenError.message || 'Failed to create token');
return;
}
// Create payment method
await payments.createPaymentMethod({
type: 'card',
card_token: token!.id,
is_default: true,
});
alert('Payment successful!');
} catch (err: any) {
setError(err.message || 'Payment failed');
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<CardElement />
{error && <div style={{ color: 'red' }}>{error}</div>}
<button type="submit" disabled={!stripe || loading}>
{loading ? 'Processing...' : 'Pay $50.00'}
</button>
</form>
);
}
export default function CheckoutPage() {
return (
<Elements stripe={stripePromise}>
<CheckoutForm />
</Elements>
);
}Server Component with Payment Data
// app/subscriptions/page.tsx
import { withPubflowAuthSSR } from '@pubflow/nextjs';
import { BridgePaymentClient } from '@pubflow/nextjs';
import { cookies } from 'next/headers';
export default withPubflowAuthSSR(
async function SubscriptionsPage({ user }) {
// Initialize Bridge Payments on server
const payments = new BridgePaymentClient({
baseUrl: process.env.BRIDGE_URL!,
getSessionId: async () => {
return cookies().get('pubflow_session_id')?.value || null;
},
});
// Fetch subscriptions on server
const subscriptions = await payments.listSubscriptions();
return (
<div>
<h1>Your Subscriptions</h1>
<ul>
{subscriptions.data.map((sub) => (
<li key={sub.id}>
{sub.plan_id} - {sub.status}
</li>
))}
</ul>
</div>
);
},
{ redirectTo: '/login' }
);Payment Management API Routes
// app/api/payment-methods/route.ts
import { withPubflow } from '@pubflow/nextjs';
import { BridgePaymentClient } from '@pubflow/nextjs';
export const GET = withPubflow(async (req, { user }) => {
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const payments = new BridgePaymentClient({
baseUrl: process.env.BRIDGE_URL!,
getSessionId: async () => req.cookies.get('pubflow_session_id')?.value || null,
});
const methods = await payments.listPaymentMethods();
return Response.json(methods);
});
export const POST = withPubflow(async (req, { user }) => {
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const body = await req.json();
const payments = new BridgePaymentClient({
baseUrl: process.env.BRIDGE_URL!,
getSessionId: async () => req.cookies.get('pubflow_session_id')?.value || null,
});
const method = await payments.createPaymentMethod(body);
return Response.json(method, { status: 201 });
});// app/api/payment-methods/[id]/route.ts
import { withPubflow } from '@pubflow/nextjs';
import { BridgePaymentClient } from '@pubflow/nextjs';
export const DELETE = withPubflow(async (req, { user }, { params }) => {
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const payments = new BridgePaymentClient({
baseUrl: process.env.BRIDGE_URL!,
getSessionId: async () => req.cookies.get('pubflow_session_id')?.value || null,
});
await payments.deletePaymentMethod(params.id);
return Response.json({ success: true });
});Available Payment Features
All payment features from @pubflow/core are available:
- ✅ Payment Intents - Create and manage payment intents
- ✅ Payment Methods - Save and manage cards, bank accounts
- ✅ Subscriptions - Recurring billing and subscription management
- ✅ Customers - Customer profile management
- ✅ Addresses - Billing and shipping addresses
- ✅ Organizations - Multi-tenant organization support
- ✅ Payment History - View past payments and transactions
- ✅ Guest Checkout - Accept payments without registration
- ✅ Server-Side Processing - Secure payment processing on the server
- ✅ Client-Side Integration - Seamless client-side payment flows
See the Bridge Payments documentation for complete details.
Environment Variables
# .env.local
NEXT_PUBLIC_BACKEND_URL=https://your-backend.com
NEXT_PUBLIC_BRIDGE_URL=https://your-bridge-payments.com
NEXT_PUBLIC_STRIPE_KEY=pk_test_...
# Server-only (not exposed to client)
BRIDGE_URL=https://your-bridge-payments.comTypeScript Support
Full TypeScript support with type inference:
import { useBridgeQuery } from '@pubflow/nextjs';
interface User {
id: string;
name: string;
email: string;
}
export default function UsersPage() {
const { data } = useBridgeQuery<User[]>('/api/users');
// data is typed as User[] | undefined
}Best Practices
1. Use Server Components When Possible
Server components are faster and more SEO-friendly:
// ✅ Good - Server component
import { withPubflowAuthSSR } from '@pubflow/nextjs';
export default withPubflowAuthSSR(async function Page({ user, api }) {
const data = await api.get('/api/data');
return <div>{data.title}</div>;
});
// ❌ Avoid - Client component for static data
'use client';
import { useBridgeQuery } from '@pubflow/nextjs';
export default function Page() {
const { data } = useBridgeQuery('/api/data');
return <div>{data?.title}</div>;
}2. Use API Routes for Backend Communication
Don't call your Flowfull backend directly from client components:
// ✅ Good - API route
// app/api/users/route.ts
export const GET = withPubflow(async (req, { api }) => {
const users = await api.get('/api/users');
return Response.json(users);
});
// app/users/page.tsx
'use client';
const { data } = useBridgeQuery('/api/users'); // Calls Next.js API route
// ❌ Avoid - Direct backend call from client
const { data } = useBridgeQuery('https://backend.com/api/users');3. Handle Authentication Errors
<PubflowProvider
url={process.env.NEXT_PUBLIC_BACKEND_URL!}
onSessionExpired={() => {
window.location.href = '/login';
}}
onAuthError={(error) => {
console.error('Auth error:', error);
if (error.message.includes('401')) {
window.location.href = '/login';
}
}}
>Next Steps
- Authentication Guide - Learn about authentication
- API Calls Guide - Master the Bridge API
- Bridge Payments - Complete payment processing guide
- Examples - See more examples
- @pubflow/react - React documentation (same hooks/components API)
- @pubflow/core - Core package documentation