@pubflow/nextjs
Next.js integration for Flowfull Clients with SSR support, API route helpers, middleware, and cookie-based session management.
Installation
npm install @pubflow/core @pubflow/nextjs swrQuick 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'],
};Environment Variables
# .env.local
NEXT_PUBLIC_BACKEND_URL=https://your-backend.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
- Examples - See more examples
- @pubflow/react - React documentation (same hooks/components API)