AI Application with Vercel and NextJs with Moesif for Analytics

Excellent Developer Experience (DX) & Rapid Prototyping

Next.js (a React framework) and Vercel (the platform created by the Next.js team) are renowned for providing a smooth and efficient development workflow. This allows developers to quickly build, test, and iterate on interfaces for AI features, which is crucial in the rapidly evolving AI space. Features like Fast Refresh, easy setup, and integrated tooling speed things up considerably.

Performance Optimized for Interactive Experiences

AI applications often involve fetching data from models, which can sometimes have latency. Next.js offers various rendering strategies:

  • SSR (Server-Side Rendering)
  • SSG (Static Site Generation)
  • ISR (Incremental Static Regeneration)
  • Client-Side Rendering

It also leverages React Server Components, allowing developers to optimize loading times and create snappy user interfaces, even when interacting with potentially slow AI backends.

For more details on rendering strategies, check out the Next.js documentation.

Streaming UI

This is a key factor. Frameworks like the Vercel AI SDK, built on top of Next.js and React Server Components, make it easier to stream responses directly from AI models (like large language models) to the user interface. This dramatically improves the perceived performance for things like chatbots or content generation, as the user sees results appearing token by token rather than waiting for the full response.

Vercel AI SDK

Vercel has actively invested in this space by releasing the Vercel AI SDK. This open-source library provides hooks and utilities specifically designed to simplify integrating AI models (from providers like OpenAI, Hugging Face, Anthropic, Cohere, etc.) into Next.js/React applications.

Key Features of the Vercel AI SDK

  • Handles common patterns like streaming text responses.
  • Manages chat history.
  • Provides UI components.

These features significantly lower the barrier to entry for building AI frontends.

Edge Functions & Global Infrastructure

Vercel’s platform heavily utilizes edge computing. Running functions (like API calls to AI models or light preprocessing) closer to the user via Edge Functions can reduce latency compared to traditional serverless functions located in a single region.

While complex AI model inference might still happen in centralized data centers, the edge is ideal for orchestrating calls, caching, and handling the UI logic quickly.

To learn more about edge computing, check out this article.

Serverless Architecture

Vercel’s serverless functions (and Edge Functions) are well-suited for the often bursty or unpredictable traffic patterns of new AI applications.

  • No need to manage servers.
  • Scaling is handled automatically.

This is great for API routes that might call AI services.

Strong Ecosystem and Community

Next.js has a large, active community and a rich ecosystem of libraries and integrations. This means developers can often find existing solutions or get help when building complex AI-powered features.

For example, you can explore the Next.js GitHub repository for community contributions and discussions.

How Analytics become more important than ever for AI Apps

Modern AI-driven applications, particularly those leveraging Large Language Models (LLMs) or other generative AI services, rely heavily on API calls—both to external model providers (like OpenAI, Anthropic, Google Gemini, etc.) and potentially to internal microservices managing AI logic. Moesif provides crucial visibility into this complex web of interactions. By capturing and analyzing every API request and response, Moesif allows developers and product teams to understand exactly how their AI features are being used. This includes monitoring the frequency of calls to different models or endpoints, tracking latency and error rates for AI responses, and understanding the overall load these features place on the system. This detailed monitoring is essential for ensuring the reliability and performance of AI functionalities, which often have variable response times and can be resource-intensive.

Beyond basic performance monitoring, Moesif offers deeper insights specifically valuable for AI applications. It allows teams to inspect the actual payloads of API calls, meaning they can analyze the prompts being sent to AI models and the responses being received, without necessarily storing sensitive data long-term. This is invaluable for debugging issues, understanding user behavior (what kinds of prompts lead to successful outcomes?), and identifying patterns in AI model usage. Furthermore, Moesif enables cost tracking by associating API usage with specific users or features, helping teams manage the often significant expenses associated with third-party AI model APIs (e.g., tracking token usage). By correlating API usage data with user information, businesses can gain insights into which customer segments are most engaged with AI features, paving the way for better product development, personalization, and cost optimization strategies.

For more information on API analytics, visit Moesif’s website.

Integrating Moesif with Next.js

This guide provides step-by-step directions on how to implement the moesif-nodejs SDK within a Next.js application, based on patterns from the official Moesif Next.js example repository and general SDK documentation.

We’ll cover two primary integration methods:

  1. Integrating with API Routes (Server-Side Runtime): Recommended for capturing detailed request/response bodies and using the full feature set of moesif-nodejs. Runs in a Node.js environment on the server. Best suited for the Pages Router or if full server-side features are needed in App Router (though requires more careful setup).
  2. Integrating with Next.js Middleware (Edge Runtime): Useful for capturing events processed at the Edge. Ideal for the App Router and Pages Router when Edge processing is desired. Note that Edge runtime has limitations, requiring manual event construction.

Prerequisites:

  • A Next.js project set up.
  • Node.js and npm (or yarn) installed.
  • A Moesif account and your Moesif Application ID. You can get one at Moesif.

Step 1: Install Moesif SDK

Navigate to your Next.js project directory in your terminal and install the moesif-nodejs package:

npm install moesif-nodejs
# or
yarn add moesif-nodejs

Step 2: Configure Moesif Application ID

Store your Moesif Application ID securely using environment variables. Create or open the .env.local file in the root of your project and add your ID:

# .env.local
MOESIF_APPLICATION_ID=YOUR_APPLICATION_ID_HERE

Replace YOUR_APPLICATION_ID_HERE with your actual Moesif Application ID.

Important: Remember to add .env.local to your .gitignore file to avoid committing sensitive credentials.

# .gitignore
.env.local

Method 1: Integrating with API Routes (Server-Side Runtime)

This method uses a Higher-Order Function (HOF) or wrapper to apply the Moesif middleware to your individual API route handlers, running in the Node.js runtime.

Step 3a. Create a Moesif Middleware Wrapper

Create a utility file (e.g., lib/moesifNode.js or utils/moesif.js) to initialize Moesif and create the wrapper function:

// lib/moesifNode.js
import moesif from 'moesif-nodejs';

const moesifOptions = {
  applicationId: process.env.MOESIF_APPLICATION_ID,

  // --- Optional configurations ---

  // Capture outgoing API Calls made by your server
  // captureOutgoingRequests: true, // Requires separate Moesif dependency

  // Function to identify users (highly recommended)
  // identifyUser: function (req, res) {
  //   // Example: Extract user ID from request object (e.g., after authentication middleware)
  //   return req.user ? req.user.id : undefined;
  // },

  // Function to identify companies/accounts
  // identifyCompany: function (req, res) {
  //    return req.user ? req.user.companyId : undefined;
  // }

  // Log request/response bodies (be mindful of sensitive data and performance)
  logBody: true,

  // Function to skip specific events from being logged
  // skip: function (req, res) {
  //   return req.url.startsWith('/_next/') || req.url.startsWith('/healthcheck');
  // }

  // Function to mask sensitive data in headers or body
  // maskContent: function (eventData) {
  //    if (eventData.request && eventData.request.headers) {
  //        eventData.request.headers['authorization'] = '**** masked ****';
  //    }
  //    // Add more masking logic as needed for body fields etc.
  //    return eventData;
  // }

  // --- Add other moesif-nodejs options as needed ---
  // See: [https://www.moesif.com/docs/server-integration/nodejs/options/](https://www.moesif.com/docs/server-integration/nodejs/options/)
};

// Initialize Moesif middleware, ensuring Application ID exists
const moesifMiddleware = process.env.MOESIF_APPLICATION_ID
  ? moesif(moesifOptions)
  : (req, res, next) => { // Fallback if no ID is configured
      console.warn('Moesif Application ID not configured. Skipping Moesif middleware.');
      if (typeof next === 'function') next();
    };

// Export the initialized middleware function directly
export default moesifMiddleware;

// Optional: Helper HOF to simplify wrapping Next.js Pages Router API handlers
export function withMoesifPagesApi(handler) {
  return (req, res) => {
    // Ensure Application ID is present before applying middleware
    if (!process.env.MOESIF_APPLICATION_ID) {
        console.warn('Moesif Application ID not configured. Skipping Moesif wrapper.');
        return handler(req, res);
    }
    // Apply the Moesif middleware logic before the handler
    moesifMiddleware(req, res, () => {
        // Call the original handler after Moesif logic runs
        // Ensure handler result is returned (esp. for async handlers)
        return handler(req, res);
    });
  };
}

Step 3b. Apply the Wrapper to your API Routes

Import the wrapper into your API route files and wrap your handler function.

  • For Pages Router (pages/api/your-route.js): Use the withMoesifPagesApi helper for cleaner code.

      // pages/api/hello.js
      import { withMoesifPagesApi } from '../../lib/moesifNode'; // Adjust path if needed
    
      function handler(req, res) {
        // Your API logic here
        console.log('API handler executed');
        res.status(200).json({ message: 'Hello from Next.js Pages API!' });
      }
    
      // Wrap the default exported handler
      export default withMoesifPagesApi(handler);
    
  • For App Router (app/api/your-route/route.js): Directly using the moesif-nodejs middleware function (which expects Node.js req/res objects) with App Router’s Request/Response objects requires careful adaptation or shimming. Using Next.js Middleware (Method 2) is generally the recommended and simpler approach for App Router.

    If you must use it server-side within an App Router route handler, you would need to manually invoke the middleware logic, potentially creating shimmed req/res objects or using lower-level Moesif functions. This is complex and error-prone.

    Recommendation: For App Router, strongly prefer Method 2 (Next.js Middleware).

Method 2: Integrating with Next.js Middleware (Edge Runtime)

This method uses the built-in Next.js Middleware (middleware.ts) which often runs on the Edge runtime. This requires manually constructing and sending event data using MoesifController because the standard Express-style middleware function isn’t directly compatible with the Edge environment and NextRequest/NextResponse objects.

Step 4a. Create the Middleware File

Create a file named middleware.ts (or .js) at the root of your project (or inside the src directory if you use one).

Step 4b. Implement Moesif Logging Logic

Paste and adapt the following code into your middleware.ts. This mirrors the approach shown in the moesif-next-js-example repository.

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { MoesifController } from 'moesif-nodejs'; // Import the controller

// Initialize Moesif Controller directly for Edge compatibility
// Ensure options here match general intent (logBody, identifyUser etc.)
// but apply logic manually below.
const moesifOptions = {
  applicationId: process.env.MOESIF_APPLICATION_ID,
  logBody: true, // Example: Set config option, but body logging is handled manually below
  // Add other relevant options used in manual data construction
};

// Ensure Moesif is initialized only if the Application ID is present
const moesifController = process.env.MOESIF_APPLICATION_ID
  ? new MoesifController(moesifOptions)
  : null;

// Helper function to safely get identifyUser result
function getUserId(req: NextRequest): string | undefined {
  // Add your logic to extract user ID from the request
  // Example: using headers, cookies, or decoded JWT from another middleware
  // if (req.auth) return req.auth.userId; // Example: Clerk auth object
  // const userCookie = req.cookies.get('user-id');
  // if (userCookie) return userCookie.value;
  return undefined;
}

export async function middleware(req: NextRequest) {
  const startTime = Date.now();
  // IMPORTANT: Must call NextResponse.next() or rewrite/redirect to get response object
  const response = NextResponse.next();

  // If Moesif isn't configured, just return the response immediately
  if (!moesifController) {
    return response;
  }

  // --- Capture Request Data ---
  const reqHeaders: { [key: string]: string } = {};
  req.headers.forEach((value, key) => {
    reqHeaders[key] = value;
  });

  // Carefully read request body if enabled and necessary
  let reqBody: any = undefined;
  if (moesifOptions.logBody && req.body && req.method !== 'GET' && req.method !== 'HEAD') {
    try {
      // Clone the request to read the body non-destructively
      const reqClone = req.clone();
      const contentType = reqHeaders['content-type'];

      if (contentType?.includes('application/json')) {
        reqBody = await reqClone.json();
      } else if (contentType?.includes('application/x-www-form-urlencoded') || contentType?.includes('text/plain')) {
        reqBody = await reqClone.text(); // Log form data or text as string
      }
      // Add handling for other content types (e.g., multipart/form-data) if needed
      // Be mindful of Edge function size and memory limits for large bodies
    } catch (error) {
      if (error instanceof Error) {
        console.error("Moesif Middleware: Error reading request body:", error.message);
      } else {
         console.error("Moesif Middleware: Unknown error reading request body:", error);
      }
      // Decide how to handle body reading errors (e.g., log error message)
      reqBody = { moesif_middleware_error: 'Failed to read request body' };
    }
  }

  // --- Capture Response Data ---
  // Response data (status, headers) captured here is from NextResponse.next()
  // Modifications made by the actual page/API handler are NOT captured here.
  // For accurate final response logging, Method 1 is more reliable.
  const resHeaders: { [key: string]: string } = {};
  response.headers.forEach((value, key) => {
    resHeaders[key] = value;
  });

  // --- Construct Moesif Event Data ---
  const eventData = {
    request: {
      time: new Date(startTime).toISOString(),
      uri: req.url,
      verb: req.method,
      headers: reqHeaders,
      ip_address: req.ip,
      api_version: undefined, // Extract from URL/headers if applicable
      body: reqBody,
    },
    response: {
      time: new Date().toISOString(), // Time approximates end of middleware processing
      status: response.status, // Status from NextResponse.next()
      headers: resHeaders,      // Headers from NextResponse.next()
      body: undefined,        // Response body cannot be captured here reliably
    },
    userId: getUserId(req),   // Call your user identification logic
    // companyId: getCompanyId(req), // Call your company identification logic
    // sessionToken: req.cookies.get('session')?.value,
    // metadata: { edge_processed: true },
  };

  // --- Send Event to Moesif (Non-blocking) ---
  // Use trackEvent() and run asynchronously without awaiting
  Promise.resolve(moesifController.trackEvent(eventData)).catch(err => {
      console.error("Moesif Middleware: Error sending event:", err);
  });

  // Return the response to allow request processing to continue
  return response;
}

// --- Configure Middleware Path Matching ---
// Apply the middleware only to the paths you want to monitor
export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     * Feel free to adjust this to be more specific, e.g., only '/api/:path*'
     */
    '/((?!_next/static|_next/image|favicon.ico).*)',
  ],
};

Key considerations for Edge Middleware:

  • We use MoesifController directly, not the Express-style middleware function.
  • Event data (request details, response status/headers available at this stage) is manually constructed.
  • Reading the request body requires req.clone() and careful error handling. Be aware of potential size limits on the Edge.
  • The final response details (body, status/headers modified by the page/API handler) are not reliably captured in this middleware method after NextResponse.next() is called. Use Method 1 for full response capture.
  • The Moesif event is sent asynchronously (Promise.resolve().catch()) to avoid delaying the response to the client.
  • Customize the matcher in config to precisely control which paths trigger the middleware.

Step 5: Verification

  1. Start your Next.js development server:
    npm run dev
    # or
    yarn dev
    
  2. Trigger the API routes or pages that are configured with Moesif (based on the method and matcher you implemented). Use tools like curl, Postman, or simply access them via your browser.
  3. Log in to your Moesif account dashboard.
  4. Navigate to the Live Event Log. You should see incoming events from your Next.js application appearing within a minute or two. Verify that the request details (URL, method, headers, body if enabled) and user identification (if configured) look correct.

Step 6: Optional Configuration & Features

Moesif offers many configuration options to tailor its behavior. Explore these in the moesifOptions object (lib/moesifNode.js for Method 1, or adapt for manual construction in middleware.ts for Method 2):

  • User/Company Identification: Implement identifyUser and identifyCompany functions (highly recommended for associating traffic with users/accounts).
  • Body Logging: Toggle logBody: true/false.
  • Content Masking: Implement maskContent to hide sensitive data in logged headers or bodies.
  • Skipping Events: Implement skip to prevent certain requests (e.g., health checks, static assets) from being logged.
  • Metadata: Add custom metadata to events using the metadata field in Method 2 or the getMetadata option in Method 1.
  • Session Tokens: Associate events with user sessions using sessionToken (Method 2) or getSessionToken (Method 1).
  • Outgoing Request Capture: Monitor API calls made by your Next.js server (requires moesif-nodejs-capture-outgoing).

Refer to the official Moesif Node.js SDK Options documentation for comprehensive details: https://www.moesif.com/docs/server-integration/nodejs/options/

Choose the integration method that best suits your Next.js architecture (Pages Router vs. App Router) and your specific logging requirements (Edge vs. Server-side, full response capture needs).

A detailed example repo for NextJs with Moesif can be found on github.

Monetize AI Apps with Moesif Monetize AI Apps with Moesif

Monetize AI Apps with Moesif

Learn More