Vercel Plugin
Vercel integration with edge functions and analytics
Vercel Plugin
The @entrolytics/vercel-plugin provides seamless Vercel integration with edge functions, automatic analytics collection, and deployment-time configuration.
Installation
npm install @entrolytics/vercel-pluginQuick Start
Configure vercel.json
{
"functions": {
"api/**/*.ts": {
"runtime": "@vercel/node"
}
},
"build": {
"env": {
"ENTROLYTICS_WEBSITE_ID": "@entrolytics-website-id",
"ENTROLYTICS_API_KEY": "@entrolytics-api-key"
}
},
"postbuild": "entrolytics-vercel-build"
}Environment Variables
# Vercel Environment Variables
ENTROLYTICS_WEBSITE_ID=your-website-id
ENTROLYTICS_API_KEY=your-api-key
ENTROLYTICS_HOST=https://entrolytics.click
ENTROLYTICS_DEBUG=falseEdge Function Integration
// api/track.ts
import { VercelRequest, VercelResponse } from '@vercel/node'
import { EntrolyticsVercel } from '@entrolytics/vercel-plugin'
const entrolytics = new EntrolyticsVercel({
websiteId: process.env.ENTROLYTICS_WEBSITE_ID!,
apiKey: process.env.ENTROLYTICS_API_KEY
})
export default async function handler(req: VercelRequest, res: VercelResponse) {
// Track API requests
await entrolytics.track('api_request', {
method: req.method,
path: req.url,
userAgent: req.headers['user-agent'],
ip: req.headers['x-forwarded-for'] || req.headers['x-real-ip']
})
// Your API logic here
res.status(200).json({ message: 'Success' })
}Middleware Integration
// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
import { EntrolyticsVercelMiddleware } from '@entrolytics/vercel-plugin'
const analytics = new EntrolyticsVercelMiddleware({
websiteId: process.env.ENTROLYTICS_WEBSITE_ID!,
apiKey: process.env.ENTROLYTICS_API_KEY
})
export async function middleware(req: NextRequest) {
// Track page views
if (req.nextUrl.pathname.startsWith('/')) {
await analytics.trackPageView(req, {
referrer: req.referrer,
userAgent: req.headers.get('user-agent'),
country: req.geo?.country,
region: req.geo?.region,
city: req.geo?.city
})
}
// Track API requests
if (req.nextUrl.pathname.startsWith('/api/')) {
await analytics.trackApiRequest(req, {
method: req.method,
endpoint: req.nextUrl.pathname,
apiVersion: req.headers.get('api-version')
})
}
return NextResponse.next()
}
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico).*)']
}Configuration
Plugin Configuration
// entrolytics.config.js
module.exports = {
websiteId: process.env.ENTROLYTICS_WEBSITE_ID,
apiKey: process.env.ENTROLYTICS_API_KEY,
host: process.env.ENTROLYTICS_HOST || 'https://entrolytics.click',
// Edge function configuration
edgeFunctions: {
enabled: true,
trackRequests: true,
trackErrors: true,
trackPerformance: true,
excludePatterns: ['/_next/*', '/api/health']
},
// Build-time analytics
buildAnalytics: {
enabled: true,
trackBuildTime: true,
trackBundleSize: true,
trackDependencies: true
},
// Deployment analytics
deploymentAnalytics: {
enabled: true,
trackDeployments: true,
trackEnvironment: true,
trackPerformance: true
},
// Debug mode
debug: process.env.NODE_ENV === 'development'
}Vercel Configuration
{
"version": 2,
"builds": [
{
"src": "api/**/*.ts",
"use": "@vercel/node"
},
{
"src": "package.json",
"use": "@vercel/static-build",
"config": {
"distDir": "out"
}
}
],
"functions": {
"api/**/*.ts": {
"maxDuration": 30,
"memory": 1024
}
},
"routes": [
{
"src": "/api/(.*)",
"dest": "/api/$1"
}
],
"env": {
"ENTROLYTICS_WEBSITE_ID": "@entrolytics-website-id",
"ENTROLYTICS_API_KEY": "@entrolytics-api-key",
"ENTROLYTICS_HOST": "@entrolytics-host"
},
"build": {
"env": {
"ENTROLYTICS_WEBSITE_ID": "@entrolytics-website-id",
"ENTROLYTICS_API_KEY": "@entrolytics-api-key"
}
}
}API Reference
Core Classes
class EntrolyticsVercel {
constructor(config: VercelConfig)
// Event tracking
track(event: string, properties?: Record<string, any>): Promise<void>
trackBatch(events: AnalyticsEvent[]): Promise<void>
page(url: string, properties?: Record<string, any>): Promise<void>
// Request tracking
trackRequest(
req: VercelRequest,
properties?: Record<string, any>
): Promise<void>
trackApiRequest(
req: VercelRequest,
properties?: Record<string, any>
): Promise<void>
trackPageView(
req: NextRequest,
properties?: Record<string, any>
): Promise<void>
// Performance tracking
trackPerformance(
metric: string,
value: number,
properties?: Record<string, any>
): Promise<void>
trackWebVitals(metrics: WebVitalsMetrics): Promise<void>
// Error tracking
trackError(error: Error, context?: Record<string, any>): Promise<void>
trackUnhandledRejection(event: PromiseRejectionEvent): Promise<void>
// Build analytics
trackBuildStart(): Promise<void>
trackBuildComplete(metrics: BuildMetrics): Promise<void>
trackDeployment(deploymentInfo: DeploymentInfo): Promise<void>
}
class EntrolyticsVercelMiddleware {
constructor(config: VercelConfig)
trackPageView(
req: NextRequest,
properties?: Record<string, any>
): Promise<void>
trackApiRequest(
req: NextRequest,
properties?: Record<string, any>
): Promise<void>
trackPerformance(req: NextRequest, metrics: PerformanceMetrics): Promise<void>
}Type Definitions
interface VercelConfig {
websiteId: string
apiKey?: string
host?: string
edgeFunctions?: {
enabled?: boolean
trackRequests?: boolean
trackErrors?: boolean
trackPerformance?: boolean
excludePatterns?: string[]
}
buildAnalytics?: {
enabled?: boolean
trackBuildTime?: boolean
trackBundleSize?: boolean
trackDependencies?: boolean
}
deploymentAnalytics?: {
enabled?: boolean
trackDeployments?: boolean
trackEnvironment?: boolean
trackPerformance?: boolean
}
debug?: boolean
}
interface BuildMetrics {
buildTime: number
bundleSize: number
dependencies: string[]
environment: string
}
interface DeploymentInfo {
url: string
environment: string
commit: string
branch: string
timestamp: Date
}
interface WebVitalsMetrics {
lcp?: number // Largest Contentful Paint
fid?: number // First Input Delay
cls?: number // Cumulative Layout Shift
fcp?: number // First Contentful Paint
ttfb?: number // Time to First Byte
}Advanced Usage
Edge Function Analytics
// api/analytics/edge.ts
import { VercelRequest, VercelResponse } from '@vercel/node'
import { EntrolyticsVercel } from '@entrolytics/vercel-plugin'
const entrolytics = new EntrolyticsVercel({
websiteId: process.env.ENTROLYTICS_WEBSITE_ID!,
apiKey: process.env.ENTROLYTICS_API_KEY,
edgeFunctions: {
enabled: true,
trackPerformance: true,
trackErrors: true
}
})
export default async function handler(req: VercelRequest, res: VercelResponse) {
const startTime = Date.now()
try {
// Track incoming request
await entrolytics.trackRequest(req, {
function: 'analytics-edge',
region: process.env.VERCEL_REGION || 'unknown',
coldStart: !globalThis.warm
})
// Your business logic
const result = await processAnalyticsData(req.body)
// Track performance
const duration = Date.now() - startTime
await entrolytics.trackPerformance('function_duration', duration, {
function: 'analytics-edge',
success: true
})
// Mark as warm for subsequent requests
globalThis.warm = true
res.status(200).json(result)
} catch (error) {
// Track error
await entrolytics.trackError(error as Error, {
function: 'analytics-edge',
region: process.env.VERCEL_REGION
})
res.status(500).json({ error: 'Internal server error' })
}
}Next.js Integration
// pages/_app.tsx
import type { AppProps } from 'next/app'
import { useEffect } from 'react'
import { EntrolyticsVercel } from '@entrolytics/vercel-plugin'
const entrolytics = new EntrolyticsVercel({
websiteId: process.env.NEXT_PUBLIC_ENTROLYTICS_WEBSITE_ID!,
})
export default function App({ Component, pageProps }: AppProps) {
useEffect(() => {
// Track client-side performance
if ('web-vitals' in window) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS((metric) => entrolytics.trackPerformance('CLS', metric.value))
getFID((metric) => entrolytics.trackPerformance('FID', metric.value))
getFCP((metric) => entrolytics.trackPerformance('FCP', metric.value))
getLCP((metric) => entrolytics.trackPerformance('LCP', metric.value))
getTTFB((metric) => entrolytics.trackPerformance('TTFB', metric.value))
})
}
}, [])
return <Component {...pageProps} />
}
// pages/api/track.ts
import type { NextApiRequest, NextApiResponse } from 'next'
import { EntrolyticsVercel } from '@entrolytics/vercel-plugin'
const entrolytics = new EntrolyticsVercel({
websiteId: process.env.ENTROLYTICS_WEBSITE_ID!,
apiKey: process.env.ENTROLYTICS_API_KEY!,
})
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' })
}
try {
const { event, properties } = req.body
await entrolytics.track(event, {
...properties,
source: 'client-api',
timestamp: new Date().toISOString(),
vercelRegion: process.env.VERCEL_REGION,
})
res.status(200).json({ success: true })
} catch (error) {
console.error('Analytics tracking error:', error)
res.status(500).json({ error: 'Failed to track event' })
}
}Build-time Analytics
// scripts/build-analytics.js
const { EntrolyticsVercel } = require('@entrolytics/vercel-plugin')
const entrolytics = new EntrolyticsVercel({
websiteId: process.env.ENTROLYTICS_WEBSITE_ID,
apiKey: process.env.ENTROLYTICS_API_KEY,
buildAnalytics: {
enabled: true,
trackBuildTime: true,
trackBundleSize: true,
trackDependencies: true
}
})
async function trackBuildStart() {
console.log('📊 Tracking build start...')
await entrolytics.trackBuildStart()
console.log('✅ Build start tracked')
}
async function trackBuildComplete(buildMetrics) {
console.log('📊 Tracking build completion...')
await entrolytics.trackBuildComplete({
buildTime: buildMetrics.duration,
bundleSize: buildMetrics.bundleSize,
dependencies: buildMetrics.dependencies,
environment: process.env.NODE_ENV,
vercelEnvironment: process.env.VERCEL_ENV
})
console.log('✅ Build completion tracked')
}
// Export for use in build scripts
module.exports = {
trackBuildStart,
trackBuildComplete
}// package.json scripts
{
"scripts": {
"build": "node scripts/track-build-start.js && next build && node scripts/track-build-complete.js",
"postbuild": "entrolytics-vercel-build"
}
}Deployment Analytics
// scripts/deployment-analytics.js
const { EntrolyticsVercel } = require('@entrolytics/vercel-plugin')
const entrolytics = new EntrolyticsVercel({
websiteId: process.env.ENTROLYTICS_WEBSITE_ID,
apiKey: process.env.ENTROLYTICS_API_KEY,
deploymentAnalytics: {
enabled: true,
trackDeployments: true,
trackEnvironment: true,
trackPerformance: true
}
})
async function trackDeployment(deploymentInfo) {
console.log('📊 Tracking deployment...')
await entrolytics.trackDeployment({
url: deploymentInfo.url,
environment: deploymentInfo.environment,
commit: deploymentInfo.commit,
branch: deploymentInfo.branch,
timestamp: new Date(),
vercelEnvironment: process.env.VERCEL_ENV,
vercelRegion: process.env.VERCEL_REGION,
buildTime: deploymentInfo.buildTime
})
console.log('✅ Deployment tracked')
}
// Usage in CI/CD
if (process.env.VERCEL === '1') {
trackDeployment({
url: process.env.VERCEL_URL,
environment: process.env.VERCEL_ENV,
commit: process.env.VERCEL_GIT_COMMIT_SHA,
branch: process.env.VERCEL_GIT_COMMIT_REF,
buildTime: process.env.BUILD_TIME
})
}Real-time Analytics Dashboard
// api/analytics/dashboard.ts
import { VercelRequest, VercelResponse } from '@vercel/node'
import { EntrolyticsVercel } from '@entrolytics/vercel-plugin'
const entrolytics = new EntrolyticsVercel({
websiteId: process.env.ENTROLYTICS_WEBSITE_ID!,
apiKey: process.env.ENTROLYTICS_API_KEY!,
})
export default async function handler(req: VercelRequest, res: VercelResponse) {
if (req.method !== 'GET') {
return res.status(405).json({ error: 'Method not allowed' })
}
try {
const { timeRange = '1h', metrics = 'all' } = req.query
const dashboardData = await entrolytics.getDashboardData({
timeRange: timeRange as string,
metrics: metrics as string[],
filters: {
vercelEnvironment: process.env.VERCEL_ENV,
vercelRegion: process.env.VERCEL_REGION,
},
})
res.status(200).json(dashboardData)
} catch (error) {
console.error('Dashboard data error:', error)
res.status(500).json({ error: 'Failed to fetch dashboard data' })
}
}
// components/AnalyticsDashboard.tsx
import { useEffect, useState } from 'react'
interface DashboardData {
totalEvents: number
uniqueUsers: number
topPages: Array<{ page: string; views: number }>
performanceMetrics: {
avgResponseTime: number
errorRate: number
uptime: number
}
realTimeEvents: Array<{
timestamp: string
event: string
properties: Record<string, any>
}>
}
export default function AnalyticsDashboard() {
const [data, setData] = useState<DashboardData | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const fetchDashboardData = async () => {
try {
const response = await fetch('/api/analytics/dashboard?timeRange=1h')
const dashboardData = await response.json()
setData(dashboardData)
} catch (error) {
console.error('Failed to fetch dashboard data:', error)
} finally {
setLoading(false)
}
}
fetchDashboardData()
// Refresh every 30 seconds
const interval = setInterval(fetchDashboardData, 30000)
return () => clearInterval(interval)
}, [])
if (loading) return <div>Loading analytics...</div>
if (!data) return <div>Failed to load analytics</div>
return (
<div className="analytics-dashboard">
<div className="metrics-grid">
<div className="metric-card">
<h3>Total Events</h3>
<p className="metric-value">{data.totalEvents.toLocaleString()}</p>
</div>
<div className="metric-card">
<h3>Unique Users</h3>
<p className="metric-value">{data.uniqueUsers.toLocaleString()}</p>
</div>
<div className="metric-card">
<h3>Avg Response Time</h3>
<p className="metric-value">{data.performanceMetrics.avgResponseTime}ms</p>
</div>
<div className="metric-card">
<h3>Error Rate</h3>
<p className="metric-value">{(data.performanceMetrics.errorRate * 100).toFixed(2)}%</p>
</div>
</div>
<div className="charts-section">
<div className="chart-container">
<h3>Top Pages</h3>
{/* Render top pages chart */}
</div>
<div className="chart-container">
<h3>Real-time Events</h3>
{/* Render real-time events */}
</div>
</div>
</div>
)
}Testing
Unit Tests
import { EntrolyticsVercel } from '@entrolytics/vercel-plugin'
import { VercelRequest, VercelResponse } from '@vercel/node'
describe('EntrolyticsVercel', () => {
let entrolytics: EntrolyticsVercel
let mockReq: Partial<VercelRequest>
let mockRes: Partial<VercelResponse>
beforeEach(() => {
entrolytics = new EntrolyticsVercel({
websiteId: 'test-website-id',
apiKey: 'test-api-key',
debug: true
})
mockReq = {
method: 'GET',
url: '/api/test',
headers: {
'user-agent': 'test-agent',
'x-forwarded-for': '192.168.1.1'
}
}
mockRes = {
status: jest.fn().mockReturnThis(),
json: jest.fn().mockReturnThis()
}
})
test('should track API requests', async () => {
await entrolytics.trackRequest(mockReq as VercelRequest, {
custom: 'property'
})
// Verify tracking was called
expect(entrolytics.track).toHaveBeenCalledWith('api_request', {
method: 'GET',
path: '/api/test',
userAgent: 'test-agent',
ip: '192.168.1.1',
custom: 'property'
})
})
test('should track performance metrics', async () => {
await entrolytics.trackPerformance('response_time', 150, {
endpoint: '/api/test'
})
expect(entrolytics.track).toHaveBeenCalledWith('performance_metric', {
metric: 'response_time',
value: 150,
endpoint: '/api/test'
})
})
test('should track errors with context', async () => {
const error = new Error('Test error')
await entrolytics.trackError(error, {
endpoint: '/api/test',
method: 'GET'
})
expect(entrolytics.track).toHaveBeenCalledWith('error_occurred', {
error: 'Test error',
stack: error.stack,
endpoint: '/api/test',
method: 'GET'
})
})
})Integration Tests
describe('Vercel Integration', () => {
let app: Express
let entrolytics: EntrolyticsVercel
beforeAll(async () => {
entrolytics = new EntrolyticsVercel({
websiteId: 'integration-test-id',
apiKey: 'integration-test-key'
})
app = express()
app.use(express.json())
// Analytics middleware
app.use(async (req, res, next) => {
await entrolytics.trackRequest(req, {
middleware: 'express'
})
next()
})
app.get('/api/test', (req, res) => {
res.json({ message: 'Success' })
})
})
test('should track API requests through middleware', async () => {
const response = await request(app).get('/api/test')
expect(response.status).toBe(200)
expect(response.body.message).toBe('Success')
// Verify analytics were tracked
expect(entrolytics.track).toHaveBeenCalledWith('api_request', {
method: 'GET',
path: '/api/test',
middleware: 'express'
})
})
})Performance Optimization
Edge Function Optimization
// Optimized edge function
export default async function handler(req: VercelRequest, res: VercelResponse) {
// Early return for health checks
if (req.url === '/api/health') {
return res.status(200).json({ status: 'ok' })
}
// Track asynchronously to avoid blocking
entrolytics.trackRequest(req).catch(console.error)
// Your business logic
const result = await processRequest(req)
res.status(200).json(result)
}
// Warm-up function for edge functions
export async function warmUp() {
// Pre-warm the analytics client
await entrolytics.warmUp()
// Pre-warm database connections
await warmUpDatabase()
}Caching Strategy
// Analytics with caching
class CachedAnalytics {
private cache = new Map<string, { data: any; timestamp: number }>()
private ttl = 60000 // 1 minute
async getDashboardData(timeRange: string): Promise<any> {
const cacheKey = `dashboard:${timeRange}`
const cached = this.cache.get(cacheKey)
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data
}
const data = await entrolytics.getDashboardData({ timeRange })
this.cache.set(cacheKey, { data, timestamp: Date.now() })
return data
}
clearCache(): void {
this.cache.clear()
}
}Troubleshooting
Best Practices
Migration Guide
From Manual Vercel Setup
// Before - Manual setup
import fetch from 'node-fetch'
await fetch('https://entrolytics.click/track', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
websiteId: process.env.WEBSITE_ID,
event: 'test_event',
properties: {}
})
})
// After - Entrolytics Vercel Plugin
import { EntrolyticsVercel } from '@entrolytics/vercel-plugin'
const entrolytics = new EntrolyticsVercel({
websiteId: process.env.ENTROLYTICS_WEBSITE_ID!
})
await entrolytics.track('test_event', {})From Other Analytics Plugins
// Before - Other Vercel analytics
import { Analytics } from 'other-vercel-analytics'
const analytics = new Analytics({
apiKey: process.env.ANALYTICS_API_KEY
})
analytics.track('event', properties)
// After - Entrolytics Vercel Plugin
import { EntrolyticsVercel } from '@entrolytics/vercel-plugin'
const entrolytics = new EntrolyticsVercel({
websiteId: process.env.ENTROLYTICS_WEBSITE_ID!
})
await entrolytics.track('event', properties)
// Additional Vercel-specific features
await entrolytics.trackBuildComplete(buildMetrics)
await entrolytics.trackDeployment(deploymentInfo)Vercel plugin for Entrolytics - First-party growth analytics for the edge