Step 27 of 36 (75% complete)
Architecture Overview
Our implementation uses:
- Optimizely SDK: For feature flag management and experimentation - https://www.npmjs.com/package/@optimizely/optimizely-sdk
- Vercel Flags: For seamless integration with Next.js and deployment - https://www.npmjs.com/package/flags
- Next.js App Router: For server-side rendering and dynamic content
- Webhook Integration: For real-time datafile updates
Setting Up the Foundation
1. Environment Variables
OPTIMIZELY_FEATURE_EXP_API_KEY=your_sdk_key_here OPTIMIZELY_WEBHOOK_SECRET=your_webhook_secret_here
2. Core Optimizely Integration
The foundation of our implementation is the Optimizely instance creation and datafile management:
// lib/optimizely-feature-exp/index.ts export async function fetchDatafileFromCDN() { const sdkKey = process.env.OPTIMIZELY_FEATURE_EXP_API_KEY; try { const response = await fetch(`https://cdn.optimizely.com/datafiles/${sdkKey}.json`, { next: { tags: [OPTIMIZELY_DATAFILE_TAG] } }); return await response.json(); } catch (error) { console.log(error); } }
Key Benefits:
- Caching: Uses Next.js cache tags for efficient datafile management
- Error Handling: Graceful fallback when Optimizely is unavailable
- Performance: CDN-based datafile delivery for fast loading
Experiment 1: Search Provider Selection
The Problem
You want to test whether Shopify's native search or Optimizely Graph provides better search results and user experience.
Implementation
export const shouldUseShopifySearchFlag = flag<boolean>({ key: 'use_shopify_search', defaultValue: false, description: 'Flag for using Shopify search', options: [ { value: false, label: 'Use Optimizely Graph for Search' }, { value: true, label: 'Use Shopify for Search' } ], async decide({ cookies }) { const optimizely = await getOptimizelyInstance(); let flag = false; try { if (!optimizely) { throw new Error("Failed to create client"); } await optimizely.onReady({ timeout: 500 }); const userId = getUserId(cookies); const context = optimizely.createUserContext(userId); const decision = context.decide("use_shopify_search"); flag = decision.enabled; } catch (error) { console.error("Optimizely error:", error); } finally { reportValue('use_shopify_search', flag); return flag; } } });
Usage in Components
export default async function SearchPage({ searchParams, params }) { const useShopifySearch = await shouldUseShopifySearchFlag( resolvedParams.code, precomputeFlags ); let products; if (useShopifySearch) { products = await getShopifySearchProducts(searchValue, locale, reverse, sortKey); } else { products = await getOptiSearchProducts(searchValue ?? '', locale); } return ( <div> <p>Source: {useShopifySearch ? 'Shopify' : 'Optimizely Graph'}</p> {/* Rest of component */} </div> ); }
What to Measure
- Search Result Relevance: Click-through rates on search results
- Performance: Search response times
- User Engagement: Time spent on search results pages
- Conversion: Purchase rates from search traffic
Experiment 2: Promotional Banner Display
The Problem
You want to test whether showing a promotional banner increases conversions or creates banner blindness.
Implementation
export const showPromoBannerFlag = flag<boolean>({ key: 'show_promo_banner', defaultValue: false, description: 'Flag for showing promo banner on search page', options: [ { value: false, label: 'Hide' }, { value: true, label: 'Show' } ], async decide({ cookies }) { const optimizely = await getOptimizelyInstance(); let flag = false; try { if (!optimizely) { throw new Error("Failed to create client"); } await optimizely.onReady({ timeout: 500 }); const userId = getUserId(cookies); const context = optimizely.createUserContext(userId); const decision = context.decide("show_promo_banner"); flag = decision.enabled; } catch (error) { console.error("Optimizely error:", error); } finally { reportValue('show_promo_banner', flag); return flag; } } });
Usage in Components
export default async function SearchPage({ searchParams, params }) { const showPromoBanner = await showPromoBannerFlag( resolvedParams.code, precomputeFlags ); return ( <div className="container min-h-screen-100"> {showPromoBanner && ( <section className="mx-4 flex w-full justify-center rounded-lg bg-amber-300 py-4 font-bold md:mx-6"> Optimizely One Masterclass! Get 20% off all products. </section> )} {/* Rest of component */} </div> ); }
What to Measure
- Click-Through Rate: Banner clicks vs impressions
- Conversion Impact: Purchase rates with/without banner
- User Experience: Bounce rates and session duration
- Banner Fatigue: Performance over time
Advanced Features
1. Real-Time Datafile Updates
Implement webhook handling for instant experiment updates:
// app/api/revalidate/datafile/route.ts export async function POST(req: NextRequest) { try { const text = await req.text(); const isVerified = await verifyOptimizelyWebhook(req.headers, text); if (!isVerified) { return NextResponse.json( { success: false, message: "Invalid webhook request" }, { status: 401 } ); } revalidateTag(OPTIMIZELY_DATAFILE_TAG); console.log("Revalidating Optimizely datafile tag"); return NextResponse.json({ success: true }, { status: 200 }); } catch (error) { console.error("Error processing webhook:", error); return NextResponse.json( { success: false, message: "Internal server error" }, { status: 500 } ); } }
2. User Context Management
Consistent user identification across sessions:
const getUserId = (cookies: any) => { const existingUserId = cookies.get(COOKIE_NAME_USER_ID)?.value; return existingUserId || Math.floor(Math.random() * (10000 - 1000) + 1000).toString(); };
Best Practices
1. Error Handling
- Always provide fallback values
- Log errors for debugging
- Don't let experiments break core functionality
2. Performance
- Use appropriate timeouts
- Cache datafiles efficiently
3. User Experience
- Ensure consistent experiences within sessions
- Avoid flickering between variations
- Test with real user scenarios
Conclusion
Optimizely Feature Experimentation with Next.js provides a powerful platform for data-driven development. By implementing proper error handling, performance optimization, and measurement strategies, you can:
- Reduce deployment risks
- Improve user experiences
- Make informed product decisions
- Optimize business metrics
The combination of server-side rendering, real-time updates, and robust experimentation creates a foundation for continuous improvement and innovation.
Next Steps
- Set up your Optimizely project and experiments
- Implement the flag system in your Next.js application
- Configure webhooks for real-time updates
- Define success metrics and start measuring
- Iterate based on experiment results
Remember: The goal isn't just to run experiments, but to build a culture of data-driven decision making that continuously improves your product and user experience.
Have questions? I'm here to help!