Skip to main content

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

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
  }
});
Key differences from client-side:
SettingClientServer
applicationType'web''server'
events.persistence'localStorage''memory'
IdentityAuto-managedExplicit

Next.js

Server Components (App Router)

// 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

// 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)

// 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:
// 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'
  });
}
// 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

// 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

// 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:
// 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:
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

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:
// __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();
}

Next Steps