Skip to main content
RapidDev - Software Development Agency
v0-integrationsNext.js API Route

How to Integrate Google Analytics with V0

To use Google Analytics with V0, add the GA4 tracking script to your Next.js app using the next/script component in your root layout, and create a Next.js API route to fetch reporting data from the Google Analytics Data API for custom dashboards. Your GA4 Measurement ID is a public client-side value; the Data API credentials stay server-side in Vercel environment variables.

What you'll learn

  • How to add GA4 tracking to a V0-generated Next.js app using the next/script component
  • How to configure pageview tracking that works correctly with Next.js client-side navigation
  • How to create a Next.js API route that fetches GA4 reporting data using the Data API
  • How to set up a Google service account and store credentials in Vercel environment variables
  • How to build a custom analytics dashboard in V0 that displays live GA4 data
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate16 min read25 minutesAnalyticsApril 2026RapidDev Engineering Team
TL;DR

To use Google Analytics with V0, add the GA4 tracking script to your Next.js app using the next/script component in your root layout, and create a Next.js API route to fetch reporting data from the Google Analytics Data API for custom dashboards. Your GA4 Measurement ID is a public client-side value; the Data API credentials stay server-side in Vercel environment variables.

Adding Google Analytics Tracking and Reporting to Your V0 App

Google Analytics 4 is the most widely deployed analytics platform on the web, and connecting it to a V0-generated Next.js app involves two separate concerns: tracking (sending events from your app to GA4) and reporting (reading aggregated data back from GA4 for dashboards).

Tracking is straightforward — you add the GA4 gtag.js script to your Next.js root layout using the next/script component with your Measurement ID. The key challenge is that Next.js uses client-side navigation between pages, which does not trigger a new page load. Without extra configuration, GA4 will only record the initial page visit. You need to listen to route change events and manually fire a page_view event for each navigation. V0 can generate this tracking setup for you.

Reporting — pulling data back out of GA4 to display in a custom dashboard — requires the Google Analytics Data API and a service account with credentials that must stay server-side. This is where the Next.js API route comes in: your server-side route authenticates with Google, queries the Data API for metrics like sessions, users, and page views, and returns the data to your React dashboard component. This pattern keeps your Google service account credentials secure while enabling rich custom analytics interfaces.

Integration method

Next.js API Route

V0 generates the UI components while GA4 tracking is added to the Next.js root layout using the next/script component with your Measurement ID. For custom analytics dashboards, a Next.js API route uses the @google-analytics/data SDK to query the GA4 Data API with service account credentials, keeping API keys server-side. The Measurement ID is safe as a NEXT_PUBLIC_ variable since it is intended for browser use.

Prerequisites

  • A V0 account with a Next.js project at v0.dev
  • A Google Analytics 4 property created at analytics.google.com with a Measurement ID (format: G-XXXXXXXXXX)
  • A Google Cloud project with the Google Analytics Data API enabled (required only for reporting/dashboard features)
  • A Google service account with the Viewer role on your GA4 property (required only for reporting)
  • The service account's JSON key file downloaded from Google Cloud Console

Step-by-step guide

1

Add GA4 Tracking Script to Your Next.js Root Layout

The first step is adding the GA4 tracking script to your Next.js app so every page is tracked automatically. In a V0-generated Next.js app, this goes in the root layout file at app/layout.tsx. Use Next.js's built-in next/script component rather than a plain HTML script tag. The next/script component gives you loading strategy control — use strategy='afterInteractive' for the gtag.js script so it loads after the page becomes interactive, avoiding any impact on your Core Web Vitals scores. Your GA4 Measurement ID (format: G-XXXXXXXXXX) is a public identifier that must be embedded in client-side code — it is safe and intentional to expose it. Store it as NEXT_PUBLIC_GA_MEASUREMENT_ID in your Vercel environment variables so you can use different properties for development and production without changing code. The standard gtag.js setup sends a page_view event on initial load automatically. However, Next.js uses client-side routing — when users navigate between pages, there is no browser page reload, so GA4 receives no page_view events for subsequent pages. To fix this, create a utility file at lib/gtag.ts that exports helper functions, and add a useEffect in your root layout's client component wrapper that listens to Next.js navigation events and fires a manual page_view event on each route change. Ask V0 to generate the tracking setup: it will create the Script tags in layout.tsx and the gtag utility library with pageview and event helper functions.

V0 Prompt

Add Google Analytics 4 tracking to this Next.js app. In app/layout.tsx, add two next/script tags: one to load https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX with strategy afterInteractive, and one to initialize gtag with the dataLayer. Create lib/gtag.ts that exports a pageview function calling gtag('config', GA_ID, {page_path}) and an event function calling gtag('event', ...). The GA Measurement ID should come from process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID.

Paste this in V0 chat

lib/gtag.ts
1// lib/gtag.ts
2export const GA_MEASUREMENT_ID = process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID!;
3
4export const pageview = (url: string) => {
5 window.gtag('config', GA_MEASUREMENT_ID, {
6 page_path: url,
7 });
8};
9
10export const event = ({
11 action,
12 category,
13 label,
14 value,
15}: {
16 action: string;
17 category: string;
18 label?: string;
19 value?: number;
20}) => {
21 window.gtag('event', action, {
22 event_category: category,
23 event_label: label,
24 value: value,
25 });
26};
27
28// app/layout.tsx — add inside <head> using next/script
29// <Script
30// strategy="afterInteractive"
31// src={`https://www.googletagmanager.com/gtag/js?id=${GA_MEASUREMENT_ID}`}
32// />
33// <Script
34// id="gtag-init"
35// strategy="afterInteractive"
36// dangerouslySetInnerHTML={{
37// __html: `
38// window.dataLayer = window.dataLayer || [];
39// function gtag(){dataLayer.push(arguments);}
40// gtag('js', new Date());
41// gtag('config', '${GA_MEASUREMENT_ID}', { page_path: window.location.pathname });
42// `,
43// }}
44// />

Pro tip: Add 'declare function gtag(...args: any[]): void;' to a global.d.ts file to get TypeScript to recognize the gtag function without type errors.

Expected result: Your V0 app loads the GA4 script on every page. Opening the browser's Network tab shows a request to googletagmanager.com. In GA4's Realtime report, your own visits appear within seconds.

2

Fix Pageview Tracking for Client-Side Navigation

Next.js apps navigate between pages without a full browser reload, which means GA4's automatic page_view event only fires once on the initial visit. Every subsequent navigation appears as continued activity on the first page rather than a new pageview. This is a very common issue with Next.js and GA4 that causes significantly undercounted page metrics. The fix is to listen to Next.js router events and call the GA4 config command on each route change. In the App Router (which V0 generates by default), you use the usePathname and useSearchParams hooks inside a client component to detect navigation changes, then call your pageview() function from lib/gtag.ts. Create a dedicated component called GoogleAnalytics (or similar) marked with 'use client' that handles this navigation tracking. Mount it in your root layout inside a React Suspense boundary — useSearchParams() requires Suspense in the App Router. This component renders nothing visible but runs the side effect of firing GA4 pageview events on each URL change. This pattern is specifically needed for the V0-generated App Router structure. If you ever migrate a Pages Router app to App Router, this is one of the tracking configurations you must update, as the event system differs between the two.

V0 Prompt

Create a client component at components/GoogleAnalytics.tsx that uses usePathname and useSearchParams from next/navigation to detect route changes and call the pageview() function from lib/gtag.ts with the new URL whenever the path or search params change. Mount this component in app/layout.tsx inside a Suspense boundary.

Paste this in V0 chat

components/GoogleAnalytics.tsx
1// components/GoogleAnalytics.tsx
2'use client';
3
4import { usePathname, useSearchParams } from 'next/navigation';
5import { useEffect } from 'react';
6import { pageview } from '@/lib/gtag';
7
8export function GoogleAnalytics() {
9 const pathname = usePathname();
10 const searchParams = useSearchParams();
11
12 useEffect(() => {
13 const url = pathname + searchParams.toString();
14 pageview(url);
15 }, [pathname, searchParams]);
16
17 return null;
18}
19
20// In app/layout.tsx, add inside the body:
21// import { Suspense } from 'react';
22// import { GoogleAnalytics } from '@/components/GoogleAnalytics';
23//
24// <Suspense fallback={null}>
25// <GoogleAnalytics />
26// </Suspense>

Pro tip: Wrap the GoogleAnalytics component in a conditional: only render it when process.env.NEXT_PUBLIC_GA_MEASUREMENT_ID is defined. This prevents errors in local development if you have not yet set up the environment variable.

Expected result: Navigating between pages in your app triggers new pageview events in GA4. The Realtime report shows different page paths as you navigate, and the Pages report accumulates views on each route.

3

Set Up a Google Service Account for the Data API

Displaying GA4 data in your own dashboard requires the Google Analytics Data API. Unlike the tracking script (which is public), the Data API uses server-side authentication with a Google service account. You need to create a service account in Google Cloud, grant it access to your GA4 property, and store its credentials as a Vercel environment variable. First, go to Google Cloud Console (console.cloud.google.com). If you do not have a project, create one. In the project, go to APIs & Services → Library, search for 'Google Analytics Data API', and click Enable. Next, go to APIs & Services → Credentials → Create Credentials → Service Account. Give it a name like 'v0-app-analytics-reader'. After creating it, click on the service account, go to the Keys tab, click Add Key → Create new key → JSON. Download the JSON key file — this contains the credentials your API route will use. Now grant the service account access to your GA4 property. In Google Analytics, go to Admin → Property Access Management → click the + button → Add users. Enter the service account's email address (found in the JSON file as client_email, e.g., v0-app-analytics-reader@your-project.iam.gserviceaccount.com). Grant it the Viewer role. For Vercel, you need to store the entire JSON key as a single environment variable. In Vercel Dashboard → Settings → Environment Variables, add GOOGLE_ANALYTICS_CREDENTIALS and paste the entire JSON key file contents as the value. Also add GOOGLE_ANALYTICS_PROPERTY_ID with your GA4 property ID (a numeric string like 123456789, found in GA4 Admin → Property Settings).

Pro tip: Do not store the service account JSON file in your repository. The JSON key contains a private RSA key that would give anyone with your repo read access full reporting access to your GA4 data. Always store it as a Vercel environment variable.

Expected result: Your Vercel project has GOOGLE_ANALYTICS_CREDENTIALS (the full JSON key) and GOOGLE_ANALYTICS_PROPERTY_ID set as environment variables. The service account email appears in your GA4 property's user list with Viewer role.

4

Create the Analytics Reporting API Route

Now create the Next.js API route that queries the GA4 Data API and returns formatted data to your dashboard components. Install the @google-analytics/data npm package, which is Google's official Node.js client for the GA4 Data API. The API route at app/api/analytics/report/route.ts initializes the BetaAnalyticsDataClient using the credentials from your GOOGLE_ANALYTICS_CREDENTIALS environment variable. Parse the JSON string back into an object and pass it to the client constructor — this avoids needing to write the JSON to a file on disk, which is not possible in a serverless environment. The Data API uses a structured query format with dimensions (attributes like page path, country, device category) and metrics (numbers like sessions, users, screenPageViews). In the API route, construct a runReport request specifying your property ID, date range, dimensions, and metrics. The response is a report object with rows, each row containing dimension values and metric values. Format the raw API response into a clean JSON structure before returning it to the client — this decouples your frontend from the GA4 API response format and lets you add caching logic later without changing the component interface. Consider adding response caching: GA4 data is aggregated and does not change second-to-second. Using Next.js's built-in fetch caching or a revalidation interval on the route means you will not hit the GA4 API quota on every page load. For complex integrations with multiple report types, RapidDev's team can help configure caching and quota management strategies for production deployments.

V0 Prompt

Create a Next.js API route at app/api/analytics/report/route.ts using @google-analytics/data BetaAnalyticsDataClient. Parse GOOGLE_ANALYTICS_CREDENTIALS env var as JSON for authentication. Query the GA4 property ID from GOOGLE_ANALYTICS_PROPERTY_ID for the last 7 days: dimensions ['date', 'pagePath'], metrics ['sessions', 'activeUsers', 'screenPageViews']. Return formatted JSON with totalUsers, totalSessions, totalPageviews, and topPages array (slug and views).

Paste this in V0 chat

app/api/analytics/report/route.ts
1// app/api/analytics/report/route.ts
2import { BetaAnalyticsDataClient } from '@google-analytics/data';
3import { NextResponse } from 'next/server';
4
5export async function GET() {
6 try {
7 const credentials = JSON.parse(
8 process.env.GOOGLE_ANALYTICS_CREDENTIALS!
9 );
10
11 const analyticsDataClient = new BetaAnalyticsDataClient({
12 credentials,
13 });
14
15 const propertyId = process.env.GOOGLE_ANALYTICS_PROPERTY_ID!;
16
17 const [response] = await analyticsDataClient.runReport({
18 property: `properties/${propertyId}`,
19 dateRanges: [{ startDate: '7daysAgo', endDate: 'today' }],
20 dimensions: [{ name: 'pagePath' }],
21 metrics: [
22 { name: 'sessions' },
23 { name: 'activeUsers' },
24 { name: 'screenPageViews' },
25 ],
26 orderBys: [
27 {
28 metric: { metricName: 'screenPageViews' },
29 desc: true,
30 },
31 ],
32 limit: 10,
33 });
34
35 const topPages = response.rows?.map((row) => ({
36 path: row.dimensionValues?.[0]?.value ?? '',
37 sessions: parseInt(row.metricValues?.[0]?.value ?? '0'),
38 users: parseInt(row.metricValues?.[1]?.value ?? '0'),
39 pageviews: parseInt(row.metricValues?.[2]?.value ?? '0'),
40 })) ?? [];
41
42 const totals = response.totals?.[0];
43
44 return NextResponse.json({
45 totalSessions: parseInt(totals?.metricValues?.[0]?.value ?? '0'),
46 totalUsers: parseInt(totals?.metricValues?.[1]?.value ?? '0'),
47 totalPageviews: parseInt(totals?.metricValues?.[2]?.value ?? '0'),
48 topPages,
49 });
50 } catch (error) {
51 console.error('Analytics API error:', error);
52 return NextResponse.json(
53 { error: 'Failed to fetch analytics data' },
54 { status: 500 }
55 );
56 }
57}

Pro tip: Add next: { revalidate: 3600 } to a fetch call wrapping this route, or use Next.js route segment config export const revalidate = 3600 to cache the GA4 response for one hour and avoid hitting API quotas.

Expected result: GET /api/analytics/report returns a JSON object with totalSessions, totalUsers, totalPageviews, and a topPages array. The numbers reflect the actual traffic to your site from the past 7 days.

5

Generate Your Analytics Dashboard UI with V0

With the API route working, use V0 to generate the dashboard UI that displays the GA4 data. V0 excels at creating clean data visualization interfaces with Tailwind CSS and shadcn/ui components. Describe the dashboard layout to V0: specify the metric cards you want, whether you want charts (bar charts work well for top pages, line charts for trends over time), and the table structure for page-level data. V0 will generate a client component that fetches from your /api/analytics/report endpoint on load and renders the data. For charts, V0 typically uses Recharts, which is compatible with Next.js App Router client components. Specify in your prompt that charts should be wrapped in 'use client' components since Recharts renders in the browser. Consider asking V0 to add a date range selector so users can switch between 7-day, 30-day, and 90-day views. This requires passing a startDate and endDate query parameter to your API route and adjusting the Data API query accordingly. One important V0 limitation to be aware of: V0 generates the component structure and data-fetching logic well, but it may not perfectly match the exact shape of data your API returns. After generating the component, compare the expected data shape in the component's TypeScript types against what your /api/analytics/report endpoint actually returns and adjust field names if needed.

V0 Prompt

Create an analytics dashboard page at app/dashboard/analytics/page.tsx. Show four metric cards at the top: Total Users, Total Sessions, Total Pageviews, and Bounce Rate. Below, show a table of top 10 pages with columns for Page Path, Pageviews, Sessions, and Users. Fetch data from /api/analytics/report on mount. Use Tailwind CSS for styling with a clean card layout. Add loading skeletons and an error message if the fetch fails.

Paste this in V0 chat

Pro tip: Ask V0 to add a 'Last updated' timestamp below the dashboard so users know the data might be cached. Display the time the component last fetched data using a simple useState that records when the fetch completed.

Expected result: Your app has a working analytics dashboard that displays real GA4 data. Metric cards show actual traffic numbers and the top pages table lists your most-visited URLs with their view counts.

Common use cases

SaaS App Analytics Dashboard

A SaaS founder wants to see traffic metrics directly in their admin panel without switching to the GA4 interface. V0 generates a dashboard card showing daily active users, top pages, and traffic sources, all fetched from the GA4 Data API through a server-side API route.

V0 Prompt

Create an analytics dashboard section with three metric cards showing: total users this week, pageviews this week, and top 5 pages by views. Each card should have a number, a label, and a small sparkline chart. Fetch data from /api/analytics/report which returns JSON with users, pageviews, and topPages arrays. Show a loading skeleton while fetching and an error state if the fetch fails.

Copy this prompt to try it in V0

Marketing Landing Page with Conversion Tracking

A marketing landing page needs to track button clicks, form submissions, and scroll depth as GA4 custom events. V0 generates the landing page with event tracking calls wired to the key interactive elements, enabling conversion funnels in GA4.

V0 Prompt

Build a product landing page with a hero section, feature list, pricing table, and a contact form. When the user clicks the main CTA button, call gtag('event', 'cta_click', { button_location: 'hero' }). When the contact form is submitted, call gtag('event', 'form_submit', { form_name: 'contact' }). Import gtag as a utility function from lib/gtag.ts.

Copy this prompt to try it in V0

Blog with Real-Time Popular Posts

A content site wants to show a 'Most Read This Week' sidebar widget that reflects actual reader behavior from GA4. The Next.js API route queries the Data API for the top 5 pages by sessions in the last 7 days and returns the slugs, so the frontend can fetch and display the corresponding posts.

V0 Prompt

Create a sidebar widget called 'Popular This Week' that shows 5 post titles with their view count badge. Fetch the list from /api/analytics/popular-posts which returns an array of objects with slug and views. Show a loading state with 5 skeleton rows. Display a numbered rank badge next to each post title. Make each post title a link to its article page.

Copy this prompt to try it in V0

Troubleshooting

GA4 Realtime shows 0 active users even though the app is loading in the browser

Cause: The GA4 script is not loading, or the Measurement ID environment variable is undefined. NEXT_PUBLIC_ variables that are undefined at build time appear as the literal string 'undefined' in the deployed code rather than causing an error.

Solution: Open browser DevTools → Network tab and filter for 'googletagmanager'. If no request appears, the script is not loading. Check that NEXT_PUBLIC_GA_MEASUREMENT_ID is set in Vercel environment variables and that you have redeployed after adding it. Also confirm the script uses strategy='afterInteractive' rather than strategy='beforeInteractive'.

Only the first page visit is recorded in GA4; navigating to other pages shows no additional pageviews

Cause: The GoogleAnalytics client component for tracking route changes is missing, or the usePathname/useSearchParams effect is not firing. This is a common Next.js App Router issue — without explicit route change tracking, GA4 only receives the initial page_view from the gtag init script.

Solution: Add the GoogleAnalytics client component from Step 2 to your root layout, wrapped in a Suspense boundary. Confirm the component is actually rendering by temporarily adding a console.log inside the useEffect to verify it fires on each navigation.

GOOGLE_ANALYTICS_CREDENTIALS causes a JSON parse error or 'Unexpected token' error in the API route

Cause: When pasting the service account JSON into Vercel as an environment variable, the JSON may have been corrupted (escaped quotes, line breaks removed) or the variable was set with extra whitespace.

Solution: In Vercel Dashboard, delete the GOOGLE_ANALYTICS_CREDENTIALS variable and re-add it by pasting the raw JSON file content. Use the Vercel CLI command 'vercel env add GOOGLE_ANALYTICS_CREDENTIALS' which handles multiline values better than the dashboard UI for large JSON objects.

typescript
1// Test your JSON parsing in the API route:
2const raw = process.env.GOOGLE_ANALYTICS_CREDENTIALS!;
3try {
4 const credentials = JSON.parse(raw);
5 console.log('Parsed email:', credentials.client_email);
6} catch (e) {
7 console.error('JSON parse failed. First 100 chars:', raw.slice(0, 100));
8}

Data API returns 403 Permission denied or 'User does not have sufficient permissions'

Cause: The service account has not been added to the GA4 property, or it was added with insufficient permissions. The service account must be explicitly granted access at the GA4 property level — having permissions in Google Cloud is not enough.

Solution: In Google Analytics, go to Admin → Property Access Management → Add users. Enter the service account's client_email address exactly and assign the Viewer role. Note: it can take up to 30 minutes for permission changes to propagate.

Best practices

  • Store your GA4 Measurement ID as NEXT_PUBLIC_GA_MEASUREMENT_ID so you can use separate GA4 properties for development and production environments
  • Always use the next/script component with strategy='afterInteractive' for the GA4 script to avoid blocking page rendering and harming Core Web Vitals
  • Add the GoogleAnalytics client component in a Suspense boundary in your root layout to capture all client-side navigations as separate pageview events
  • Cache the Data API responses for at least 60 minutes using Next.js route revalidation — GA4 data is aggregated and does not need real-time refresh
  • Use GA4 custom events for key user actions (button clicks, form submits, purchases) rather than relying solely on pageview data for meaningful product analytics
  • Never expose your Google service account JSON key in client-side code or repository — always store it as a server-only Vercel environment variable without the NEXT_PUBLIC_ prefix
  • Test your analytics tracking in GA4's DebugView (Admin → DebugView) by adding the ga-debug=1 query parameter to your URL, which shows real-time event streaming

Alternatives

Frequently asked questions

Is it safe to put the GA4 Measurement ID in my Next.js code?

Yes — the Measurement ID (G-XXXXXXXXXX) is intentionally public. It is embedded in the browser-side script and visible to anyone who views your page source. Using NEXT_PUBLIC_GA_MEASUREMENT_ID is correct. What must stay private is your Google service account JSON key used for the Data API, which should never have the NEXT_PUBLIC_ prefix.

Why does GA4 show fewer pageviews than expected for my Next.js app?

This is almost always caused by missing client-side navigation tracking. Next.js uses client-side routing, so navigating between pages does not fire a new pageview event automatically. You need the GoogleAnalytics component from Step 2 that listens to route changes and manually fires pageview events. Without it, GA4 only counts the first page visit per session.

Do I need the Data API to use Google Analytics with V0?

No. The Data API is only needed if you want to display GA4 data inside your own app (custom dashboards). For standard tracking — recording visits, events, and conversions that you view in the GA4 interface — you only need the tracking script from Step 1. Most apps only need the tracking script.

How do I track custom events like button clicks in V0?

Use the event() helper from lib/gtag.ts in your onClick handlers. For example: onClick={() => event({ action: 'cta_click', category: 'engagement', label: 'hero_button' })}. GA4 records these as custom events you can see in Reports → Engagement → Events and use in conversion tracking.

Will GA4 tracking work in Vercel preview deployments?

Yes, if you set NEXT_PUBLIC_GA_MEASUREMENT_ID for the Preview environment in Vercel. Consider using a separate GA4 property for preview deployments to keep test traffic out of your production analytics data. Set different Measurement IDs per environment scope in Vercel's environment variable settings.

Can V0 generate the entire Google Analytics integration automatically?

V0 can generate the tracking script setup, the client component for route change tracking, and the dashboard UI components. However, it cannot create your Google Cloud project, enable the Data API, or generate a service account — those steps require manual work in the Google Cloud Console. V0 generates the code scaffolding; you supply the credentials.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.