Overview
The Refine SDK works in server-side environments including Node.js, serverless functions (AWS Lambda, Vercel), and SSR frameworks (Next.js, Nuxt).Server Configuration
Copy
import { Refine } from '@refine-ai/sdk';
const refine = new Refine({
apiKey: process.env.REFINE_API_KEY,
organizationId: process.env.REFINE_ORG_ID,
catalogId: process.env.REFINE_CATALOG_ID,
applicationType: 'server',
events: {
persistence: 'memory' // No localStorage on server
}
});
| Setting | Client | Server |
|---|---|---|
applicationType | 'web' | 'server' |
events.persistence | 'localStorage' | 'memory' |
| Identity | Auto-managed | Explicit |
Next.js
Server Components (App Router)
Copy
// app/search/page.tsx
import { refine } from '@/lib/refine';
export default async function SearchPage({
searchParams
}: {
searchParams: { q?: string }
}) {
const query = searchParams.q || '';
if (!query) {
return <SearchForm />;
}
const results = await refine.search.text({
query,
topK: 24
});
return (
<div>
<SearchForm initialQuery={query} />
<ProductGrid products={results.results} />
</div>
);
}
API Routes
Copy
// app/api/search/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { refine } from '@/lib/refine';
export async function GET(request: NextRequest) {
const query = request.nextUrl.searchParams.get('q');
if (!query) {
return NextResponse.json({ error: 'Query required' }, { status: 400 });
}
try {
const results = await refine.search.text({
query,
topK: parseInt(request.nextUrl.searchParams.get('limit') || '24')
});
return NextResponse.json(results);
} catch (error) {
console.error('Search error:', error);
return NextResponse.json({ error: 'Search failed' }, { status: 500 });
}
}
getServerSideProps (Pages Router)
Copy
// pages/search.tsx
import { refine } from '@/lib/refine';
import type { GetServerSideProps } from 'next';
export const getServerSideProps: GetServerSideProps = async (context) => {
const query = context.query.q as string;
if (!query) {
return { props: { results: null, query: '' } };
}
try {
const results = await refine.search.text({ query, topK: 24 });
return {
props: {
results: results.results,
totalResults: results.totalResults,
query
}
};
} catch (error) {
return { props: { results: [], query, error: 'Search failed' } };
}
};
Client-Side Hydration
Server-render search results, then hydrate for interactivity:Copy
// lib/refine.ts
import { Refine } from '@refine-ai/sdk';
// Server instance
export const refine = new Refine({
apiKey: process.env.REFINE_API_KEY!,
organizationId: process.env.REFINE_ORG_ID!,
catalogId: process.env.REFINE_CATALOG_ID!,
applicationType: 'server',
events: { persistence: 'memory' }
});
// Client instance (created dynamically)
export function createClientRefine() {
return new Refine({
apiKey: process.env.NEXT_PUBLIC_REFINE_API_KEY!,
organizationId: process.env.NEXT_PUBLIC_REFINE_ORG_ID!,
catalogId: process.env.NEXT_PUBLIC_REFINE_CATALOG_ID!,
applicationType: 'web'
});
}
Copy
// components/SearchResults.tsx
'use client';
import { useEffect, useState } from 'react';
import { createClientRefine } from '@/lib/refine';
export function SearchResults({
initialResults,
query
}: {
initialResults: any[];
query: string;
}) {
const [refine] = useState(() => createClientRefine());
useEffect(() => {
// Track the search on client
if (initialResults.length > 0) {
const context = refine.events.trackSearch(query, initialResults, {
surface: 'search_results',
totalResults: initialResults.length
});
// Set up click tracking
// ...
}
}, [query, initialResults]);
return (
<div className="product-grid">
{initialResults.map((product, index) => (
<ProductCard
key={product.productId}
product={product}
position={index}
/>
))}
</div>
);
}
AWS Lambda
Copy
// handler.ts
import { Refine } from '@refine-ai/sdk';
// Initialize outside handler for connection reuse
const refine = new Refine({
apiKey: process.env.REFINE_API_KEY!,
organizationId: process.env.REFINE_ORG_ID!,
catalogId: process.env.REFINE_CATALOG_ID!,
applicationType: 'server',
events: { persistence: 'memory' }
});
export const handler = async (event: any) => {
const query = event.queryStringParameters?.q;
if (!query) {
return {
statusCode: 400,
body: JSON.stringify({ error: 'Query required' })
};
}
try {
const results = await refine.search.text({ query, topK: 24 });
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(results)
};
} catch (error) {
console.error('Search error:', error);
return {
statusCode: 500,
body: JSON.stringify({ error: 'Search failed' })
};
}
};
Vercel Edge Functions
Copy
// api/search.ts
import { Refine } from '@refine-ai/sdk';
export const config = {
runtime: 'edge'
};
const refine = new Refine({
apiKey: process.env.REFINE_API_KEY!,
organizationId: process.env.REFINE_ORG_ID!,
catalogId: process.env.REFINE_CATALOG_ID!,
applicationType: 'server',
events: { persistence: 'memory' }
});
export default async function handler(request: Request) {
const url = new URL(request.url);
const query = url.searchParams.get('q');
if (!query) {
return new Response(JSON.stringify({ error: 'Query required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' }
});
}
const results = await refine.search.text({ query, topK: 24 });
return new Response(JSON.stringify(results), {
headers: { 'Content-Type': 'application/json' }
});
}
Identity on Server
On the server, you need to manage identity explicitly:Copy
// For user-based recommendations
export async function getUserRecommendations(userId: string) {
refine.identify(userId);
const recs = await refine.recs.forUser({
configId: 'personalized',
topK: 12
});
return recs;
}
// For server-side purchase tracking
export async function trackPurchase(userId: string, order: Order) {
refine.identify(userId);
refine.events.trackPurchase({
orderId: order.id,
value: order.total,
currency: order.currency,
items: order.items.map(item => ({
itemId: item.productId,
quantity: item.quantity,
unitPrice: item.unitPrice
}))
});
await refine.events.flush();
}
Caching Recommendations
Cache recommendations for performance:Copy
import { LRUCache } from 'lru-cache';
const recsCache = new LRUCache<string, any>({
max: 500,
ttl: 1000 * 60 * 5 // 5 minutes
});
export async function getCachedSimilarItems(productId: string) {
const cacheKey = `similar:${productId}`;
let results = recsCache.get(cacheKey);
if (results) {
return results;
}
results = await refine.recs.similarItems({
anchorId: productId,
topK: 8
});
recsCache.set(cacheKey, results);
return results;
}
Error Handling on Server
Copy
import {
RefineRateLimitError,
RefineServerError
} from '@refine-ai/sdk';
export async function handleSearchRequest(query: string) {
try {
return await refine.search.text({ query, topK: 24 });
} catch (error) {
if (error instanceof RefineRateLimitError) {
// Return 429 to let client handle retry
throw { status: 429, retryAfter: error.retryAfter };
}
if (error instanceof RefineServerError) {
// Log and return graceful error
console.error('Refine server error:', error);
throw { status: 503, message: 'Search temporarily unavailable' };
}
throw error;
}
}
Testing
Mock the SDK for tests:Copy
// __mocks__/@refine-ai/sdk.ts
export class Refine {
search = {
text: jest.fn().mockResolvedValue({
results: [
{ productId: 'sku_001', title: 'Test Product', price: 99.99 }
],
totalResults: 1
})
};
recs = {
similarItems: jest.fn().mockResolvedValue({ results: [] }),
forVisitor: jest.fn().mockResolvedValue({ results: [] }),
forUser: jest.fn().mockResolvedValue({ results: [] })
};
events = {
trackSearch: jest.fn(),
trackPurchase: jest.fn(),
flush: jest.fn()
};
identify = jest.fn();
}