Supabase
Store analytics in Supabase via PostgREST API. Works with any PostgREST-compatible endpoint for flexible Postgres storage.
Nextlytics is a server-side analytics library for Next.js. No client JavaScript, no cookies, GDPR compliant. Learn more →
Configuration
import { Nextlytics } from "@nextlytics/core/server";
import { postgrestBackend } from "@nextlytics/core/backends/postgrest";
// Optional: import Supabase client to track authenticated users
import { createClient } from "@/lib/supabase/server";
export const { middleware, analytics } = Nextlytics({
backends: [
postgrestBackend({
// Required. Your Supabase project URL + /rest/v1
url: process.env.SUPABASE_URL! + "/rest/v1",
// Required. Your Supabase service role key (for server-side writes)
apiKey: process.env.SUPABASE_SERVICE_ROLE_KEY!,
// Optional. Table name (default: "analytics")
tableName: "analytics",
}),
],
// Optional but recommended: identify authenticated users
callbacks: {
async getUser() {
const supabase = await createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) return null;
return {
userId: user.id,
traits: { email: user.email },
};
},
},
});Generic PostgREST
This backend works with any PostgREST-compatible API, not just Supabase:
postgrestBackend({
url: "https://your-postgrest-server.com",
// apiKey is optional for non-Supabase PostgREST
});Table Schema
Create the analytics table in Supabase SQL editor:
CREATE TABLE IF NOT EXISTS analytics (
event_id TEXT PRIMARY KEY,
timestamp TIMESTAMPTZ NOT NULL,
type TEXT NOT NULL,
host TEXT,
path TEXT,
method TEXT,
user_id TEXT,
anonymous_user_id TEXT,
user_email TEXT,
user_name TEXT,
ip INET,
referer TEXT,
user_agent TEXT,
locale TEXT,
parent_event_id TEXT,
server_context JSONB,
client_context JSONB,
user_traits JSONB,
properties JSONB
);
-- Indexes for common queries
CREATE INDEX idx_analytics_timestamp ON analytics(timestamp DESC);
CREATE INDEX idx_analytics_type ON analytics(type);
CREATE INDEX idx_analytics_user_id ON analytics(user_id);
-- Enable Row Level Security (recommended)
ALTER TABLE analytics ENABLE ROW LEVEL SECURITY;
-- Policy for service role (server-side writes)
CREATE POLICY "Service role can insert" ON analytics
FOR INSERT TO service_role WITH CHECK (true);
CREATE POLICY "Service role can update" ON analytics
FOR UPDATE TO service_role USING (true);If you're using Prisma, add this model to your schema.prisma instead:
model Analytics {
event_id String @id
timestamp DateTime @db.Timestamptz()
type String
host String?
path String?
method String?
user_id String?
anonymous_user_id String?
user_email String?
user_name String?
ip String? @db.Inet
referer String?
user_agent String?
locale String?
parent_event_id String?
server_context Json? @db.JsonB
client_context Json? @db.JsonB
user_traits Json? @db.JsonB
properties Json? @db.JsonB
@@index([timestamp(sort: Desc)])
@@index([type])
@@index([user_id])
@@map("analytics")
}Then run npx prisma db push or add a migration with npx prisma migrate dev.
Event Updates
The PostgREST backend supports event updates. When client context becomes available, Nextlytics updates the existing event via PATCH:
// Initial insert
POST /rest/v1/analytics
{ event_id: "abc", path: "/products", ... }
// Later update with client context
PATCH /rest/v1/analytics?event_id=eq.abc
{ client_context: { userAgent: "...", ... } }Querying with Supabase
Use Supabase's client library to query your analytics:
import { createClient } from "@supabase/supabase-js";
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!);
// Page views today
const { data } = await supabase
.from("analytics")
.select("path, count")
.eq("type", "pageView")
.gte("timestamp", new Date().toISOString().split("T")[0]);
// User events
const { data: userEvents } = await supabase
.from("analytics")
.select("*")
.eq("user_id", "user_123")
.order("timestamp", { ascending: false });Real-time Subscriptions
Supabase supports real-time subscriptions on your analytics table:
const channel = supabase
.channel("analytics")
.on("postgres_changes", { event: "INSERT", schema: "public", table: "analytics" }, (payload) => {
console.log("New event:", payload.new);
})
.subscribe();Security
Always use the service role key for server-side writes. Never expose it to the client.
For read access, create appropriate RLS policies:
-- Allow authenticated users to read their own events
CREATE POLICY "Users can read own events" ON analytics
FOR SELECT TO authenticated
USING (user_id = auth.uid()::text);Limitations
- Service role required: Writes need the service role key (not the anon key)
- PostgREST API limits: Large batch inserts may hit API limits
- No transactions: Each event is inserted individually
Ready to add server-side analytics?
Get started with Nextlytics in 3 simple steps.