W
Wasp-langā€¢3w ago
NEROX

Google Analytics doesn't track navigation between pages (SPA Google Analytics Integration)

I have run out of the Plausible trial, I want to put GAnalytics. Inside: https://docs.opensaas.sh/guides/analytics/ I have done this step: import { getDailyPageViews, getSources } from './providers/googleAnalyticsUtils'; and I have in: .env.server: GOOGLE_ANALYTICS_CLIENT_EMAIL. GOOGLE_ANALYTICS_PROPERTY_ID GOOGLE_ANALYTICS_PRIVATE_KEY .env.client: REACT_APP_GOOGLE_ANALYTICS_ID I actually pushed the secrets to fly I think I've done everything right but when I fly deploy, it tells me: šŸ --- Building SDK... ------------------------------------------------------------ [ Wasp ] ext-src/analytics/stats.ts(6,81): error TS1002: Unterminated string literal. āŒ --- [Error] Your wasp project failed to compile: ------------------------------- - SDK build failed with exit code: 2 āŒ --- [Error] Building of wasp project failed: ----------------------------------- 1 errors found. file:///home/rootall/.local/share/wasp-lang/0.14.2/data/packages/deploy/node_modules/zx/build/core.js:146 let output = new ProcessOutput(code, signal, stdout, stderr, combined, message); '\x1B[34m[\x1B[0m \x1B[34mDb\x1B[0m \x1B[34m]\x1B[0m You can now start using Prisma Client in your code. Reference: https://pris.ly/d/client\n' + '\x1B[34m[\x1B[0m \x1B[34mDb\x1B[0m \x1B[34m]\x1B[0m ```\n' + "\x1B[34m[\x1B[0m \x1B[34mDb\x1B[0m \x1B[34m]\x1B[0m import { PrismaClient } from '@prisma/client'\n" + '\x1B[34m[\x1B[0m \x1B[34mDb\x1B[0m \x1B[34m]\x1B[0m const prisma = new PrismaClient()\n' + '\x1B[34m[\x1B[0m \x1B[34mDb\x1B[0m \x1B[34m]\x1B[0m ```\x1B[33m\n' + 'āœ… --- Database successfully set up. ----------------------------------------------\n' + '\x1B[0m\n' + '\x1B[33m\n' + 'šŸ --- Building SDK... ------------------------------------------------------------\n' + '\x1B[0m\n' + '\n' + '\x1B[33m[\x1B[0m \x1B[33mWasp\x1B[0m \x1B[33m]\x1B[0m ext-src/analytics/stats.ts(6,81): error TS1002: Unterminated string literal.\x1B[31m\n' + '\n' + 'āŒ --- [Error] Your wasp project failed to compile: -------------------------------\n' + '\n' + '- SDK build failed with exit code: 2\x1B[0m\n' + '\x1B[31m\n' + '\n' + 'āŒ --- [Error] Building of wasp project failed: -----------------------------------\n' + '\n' + '1 errors found.\x1B[0m\n', _stderr: '\n' + '\x1B[35m[\x1B[0m \x1B[35mServer\x1B[0m\x1B[1m\x1B[31m!\x1B[0m\x1B[35m]\x1B[0m npm warn deprecated [email protected]: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.\n' + '\x1B[35m[\x1B[0m \x1B[35mServer\x1B[0m\x1B[1m\x1B[31m!\x1B[0m\x1B[35m]\x1B[0m npm warn deprecated @humanwhocodes/[email protected]: Use @eslint/config-array instead\n' + '\x1B[35m[\x1B[0m \x1B[35mServer\x1B[0m\x1B[1m\x1B[31m!\x1B[0m\x1B[35m]\x1B[0m npm warn deprecated [email protected]: Rimraf versions prior to v4 are no longer supported\n' + '\x1B[35m[\x1B[0m \x1B[35mServer\x1B[0m\x1B[1m\x1B[31m!\x1B[0m\x1B[35m]\x1B[0m npm warn deprecated [email protected]: Glob versions prior to v9 are no longer supported\n' + '\x1B[35m[\x1B[0m \x1B[35mServer\x1B[0m\x1B[1m\x1B[31m!\x1B[0m\x1B[35m]\x1B[0m npm warn deprecated @humanwhocodes/[email protected]: Use @eslint/object-schema instead\n' + '\x1B[35m[\x1B[0m \x1B[35mServer\x1B[0m\x1B[1m\x1B[31m!\x1B[0m\x1B[35m]\x1B[0m npm warn deprecated [email protected]: This version is no longer supported. Please see https://eslint.org/version-support for other options.', _combined: '\x1B[33m\n' + What could be wrong? I thing is related with the base 64 enconded private api key but help me @kapa.ai ma boi
OpenSaaS.sh
Analytics
Open SaaS is a free, open-source, full-stack SaaS starter kit for React + NodeJS.
12 Replies
kapa.ai
kapa.aiā€¢3w ago
Hi @NEROX. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Based on the error message you provided, it seems the issue is related to a syntax error in your TypeScript file, specifically in ext-src/analytics/stats.ts on line 6. The error suggests there's an unterminated string literal in that file. Here's what could be wrong and how you might fix it: 1. Unterminated string literal: The error message states: ext-src/analytics/stats.ts(6,81): error TS1002: Unterminated string literal. This means that on line 6 of your stats.ts file, there's a string that isn't properly closed. Double-check this line for any missing quotation marks. 2. Base64 encoded private key: You mentioned that you think it might be related to the base64 encoded private API key. If you're inserting this key as a string in your code, make sure it's properly enclosed in quotation marks and that there are no line breaks within the string. 3. Environment variables: Ensure that your environment variables are correctly set and that there are no typos in their names. The error doesn't seem to be directly related to environment variables, but it's worth double-checking. To resolve this issue: 1. Open the ext-src/analytics/stats.ts file. 2. Go to line 6 and check for any unclosed strings or syntax errors. 3. If you're using the private key directly in this file, make sure it's properly formatted as a string. 4. If the issue persists, you might want to temporarily comment out line 6 to see if the error moves to a different line, which could help pinpoint the problem. If you continue to have issues after checking these points, you might want to share the contents of your ext-src/analytics/stats.ts file (excluding any sensitive information) so that the Wasp community can provide more specific guidance. For more information on deploying Wasp apps and handling environment variables, you can refer to the Wasp deployment documentation (https://wasp-lang.dev/docs/advanced/deployment/manually). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @NEROX, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
NEROX
NEROXOPā€¢3w ago
@kapa.ai Ok, thank you very much, imagine that you just got the JSON file right now you need to convert the private key to base sixty-four and upload it to the server as a secret, then in this case you are going to do as if it were an example of how you would do it so that the encrypted key in base 64 that is so long can be put right as it should be.
kapa.ai
kapa.aiā€¢3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- Certainly! I understand you want to know how to properly handle the Google Analytics private key for use with Wasp. Here's a step-by-step example of how you might convert the private key to base64 and set it as a secret on your server: 1. First, let's assume you have the JSON file containing your Google Analytics private key. The content might look something like this:
{
"type": "service_account",
"project_id": "your-project-id",
"private_key_id": "your-private-key-id",
"private_key": "-----BEGIN PRIVATE KEY-----\nYourLongPrivateKeyHere\n-----END PRIVATE KEY-----\n",
"client_email": "[email protected]",
"client_id": "your-client-id",
"auth_uri": "<https://accounts.google.com/o/oauth2/auth",>
"token_uri": "<https://oauth2.googleapis.com/token",>
"auth_provider_x509_cert_url": "<https://www.googleapis.com/oauth2/v1/certs",>
"client_x509_cert_url": "<https://www.googleapis.com/robot/v1/metadata/x509/your-service-account-email%40your-project.iam.gserviceaccount.com">
}
{
"type": "service_account",
"project_id": "your-project-id",
"private_key_id": "your-private-key-id",
"private_key": "-----BEGIN PRIVATE KEY-----\nYourLongPrivateKeyHere\n-----END PRIVATE KEY-----\n",
"client_email": "[email protected]",
"client_id": "your-client-id",
"auth_uri": "<https://accounts.google.com/o/oauth2/auth",>
"token_uri": "<https://oauth2.googleapis.com/token",>
"auth_provider_x509_cert_url": "<https://www.googleapis.com/oauth2/v1/certs",>
"client_x509_cert_url": "<https://www.googleapis.com/robot/v1/metadata/x509/your-service-account-email%40your-project.iam.gserviceaccount.com">
}
2. Extract the private_key from this JSON file. You'll need to convert this to base64. 3. To convert the private key to base64, you can use a command-line tool like base64. Here's an example using bash:
echo -n "-----BEGIN PRIVATE KEY-----\nYourLongPrivateKeyHere\n-----END PRIVATE KEY-----\n" | base64
echo -n "-----BEGIN PRIVATE KEY-----\nYourLongPrivateKeyHere\n-----END PRIVATE KEY-----\n" | base64
This will output a long string of base64-encoded characters. 4. Now that you have the base64-encoded private key, you can set it as a secret on your Fly.io server. Use the flyctl command-line tool:
flyctl secrets set GOOGLE_ANALYTICS_PRIVATE_KEY="your-base64-encoded-private-key-here"
flyctl secrets set GOOGLE_ANALYTICS_PRIVATE_KEY="your-base64-encoded-private-key-here"
5. In your Wasp application, you can now access this secret as an environment variable. In your ext-src/analytics/stats.ts file, you might use it like this:
import { google } from 'googleapis';

const privateKey = Buffer.from(process.env.GOOGLE_ANALYTICS_PRIVATE_KEY || '', 'base64').toString('utf-8');

const auth = new google.auth.GoogleAuth({
credentials: {
client_email: process.env.GOOGLE_ANALYTICS_CLIENT_EMAIL,
private_key: privateKey,
},
scopes: ['<https://www.googleapis.com/auth/analytics.readonly'],>
});

// Rest of your Google Analytics setup code...
import { google } from 'googleapis';

const privateKey = Buffer.from(process.env.GOOGLE_ANALYTICS_PRIVATE_KEY || '', 'base64').toString('utf-8');

const auth = new google.auth.GoogleAuth({
credentials: {
client_email: process.env.GOOGLE_ANALYTICS_CLIENT_EMAIL,
private_key: privateKey,
},
scopes: ['<https://www.googleapis.com/auth/analytics.readonly'],>
});

// Rest of your Google Analytics setup code...
This approach ensures that your private key is securely stored as a secret on your server and properly decoded for use in your application. Remember to also set the GOOGLE_ANALYTICS_CLIENT_EMAIL and GOOGLE_ANALYTICS_PROPERTY_ID as secrets on your server using the flyctl secrets set command. For more information on handling secrets and environment variables in Wasp, you can refer to the Wasp deployment documentation (https://wasp-lang.dev/docs/advanced/deployment/manually). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @NEROX, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
NEROX
NEROXOPā€¢3w ago
@kapa.ai When I try to run localhost: Server ] šŸš€ "Email and password" auth initialized [ Server!] node:internal/errors:496 [ Server!] ErrorCaptureStackTrace(err); [ Server!] ^ [ Server!] [ Server!] TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined [ Server!] at __node_internal_captureLargerStackTrace (node:internal/errors:496:5) [ Server!] at new NodeError (node:internal/errors:405:5) [ Server!] at Function.from (node:buffer:325:9) [ Server!] at <anonymous> (/home/rootall/apps/minisaas-boilerplate/app/src/analytics/providers/googleAnalyticsUtils.ts:4:28) [ Server!] at ModuleJob.run (node:internal/modules/esm/module_job:195:25) [ Server!] at async ModuleLoader.import (node:internal/modules/esm/loader:337:24) [ Server!] at async loadESM (node:internal/process/esm_loader:34:7) [ Server!] at async handleMainPromise (node:internal/modules/run_main:106:12) { [ Server!] code: 'ERR_INVALID_ARG_TYPE' [ Server!] } [ Server!] [ Server!] Node.js v18.20.4 [ Server ] [nodemon] app crashed - waiting for file changes before starting...
kapa.ai
kapa.aiā€¢3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- It seems you're encountering an error when trying to run your Wasp application locally. The error message suggests that there's an issue with the Google Analytics setup, specifically in the googleAnalyticsUtils.ts file. Let's break down the problem and suggest a solution: 1. The error is occurring in /home/rootall/apps/minisaas-boilerplate/app/src/analytics/providers/googleAnalyticsUtils.ts on line 4. 2. The error message states: TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined This error typically occurs when trying to use Buffer.from() with an undefined value. It's likely that one of your environment variables for Google Analytics is not set or is undefined. Here's what you can do to fix this: 1. Check your .env.server file and ensure that all the Google Analytics-related environment variables are correctly set:
GOOGLE_ANALYTICS_CLIENT_EMAIL=your-client-email
GOOGLE_ANALYTICS_PROPERTY_ID=your-property-id
GOOGLE_ANALYTICS_PRIVATE_KEY=your-base64-encoded-private-key
GOOGLE_ANALYTICS_CLIENT_EMAIL=your-client-email
GOOGLE_ANALYTICS_PROPERTY_ID=your-property-id
GOOGLE_ANALYTICS_PRIVATE_KEY=your-base64-encoded-private-key
2. In your googleAnalyticsUtils.ts file, add some error checking for the environment variables. Here's an example of how you might modify the file:
import { google } from 'googleapis';

if (!process.env.GOOGLE_ANALYTICS_PRIVATE_KEY) {
throw new Error('GOOGLE_ANALYTICS_PRIVATE_KEY is not set in the environment');
}

const privateKey = Buffer.from(process.env.GOOGLE_ANALYTICS_PRIVATE_KEY, 'base64').toString('utf-8');

if (!process.env.GOOGLE_ANALYTICS_CLIENT_EMAIL) {
throw new Error('GOOGLE_ANALYTICS_CLIENT_EMAIL is not set in the environment');
}
const auth = new google.auth.GoogleAuth({
credentials: {
client_email: process.env.GOOGLE_ANALYTICS_CLIENT_EMAIL,
private_key: privateKey,
},
scopes: ['<https://www.googleapis.com/auth/analytics.readonly'],>
});

// Rest of your Google Analytics setup code...
import { google } from 'googleapis';

if (!process.env.GOOGLE_ANALYTICS_PRIVATE_KEY) {
throw new Error('GOOGLE_ANALYTICS_PRIVATE_KEY is not set in the environment');
}

const privateKey = Buffer.from(process.env.GOOGLE_ANALYTICS_PRIVATE_KEY, 'base64').toString('utf-8');

if (!process.env.GOOGLE_ANALYTICS_CLIENT_EMAIL) {
throw new Error('GOOGLE_ANALYTICS_CLIENT_EMAIL is not set in the environment');
}
const auth = new google.auth.GoogleAuth({
credentials: {
client_email: process.env.GOOGLE_ANALYTICS_CLIENT_EMAIL,
private_key: privateKey,
},
scopes: ['<https://www.googleapis.com/auth/analytics.readonly'],>
});

// Rest of your Google Analytics setup code...
3. Make sure you've followed the steps to convert your private key to base64 as described in the previous answer. If you haven't done this yet, you'll need to do so and update your .env.server file with the base64-encoded private key. 4. If you're using Fly.io for deployment, ensure that you've set these environment variables as secrets on your Fly.io server:
flyctl secrets set GOOGLE_ANALYTICS_PRIVATE_KEY="your-base64-encoded-private-key-here"
flyctl secrets set GOOGLE_ANALYTICS_CLIENT_EMAIL="your-client-email"
flyctl secrets set GOOGLE_ANALYTICS_PROPERTY_ID="your-property-id"
flyctl secrets set GOOGLE_ANALYTICS_PRIVATE_KEY="your-base64-encoded-private-key-here"
flyctl secrets set GOOGLE_ANALYTICS_CLIENT_EMAIL="your-client-email"
flyctl secrets set GOOGLE_ANALYTICS_PROPERTY_ID="your-property-id"
5. After making these changes, try running your application again. If you continue to experience issues, you might want to add some console.log statements in your googleAnalyticsUtils.ts file to check the values of the environment variables and ensure they're being read correctly. For more information on handling environment variables and secrets in Wasp, you can refer to the Wasp deployment documentation (https://wasp-lang.dev/docs/advanced/deployment/manually). ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @NEROX, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
NEROX
NEROXOPā€¢3w ago
Aaaaaaah, I'm acoustic @kapa.ai solved. GAnalytics is tracking, now as SPA, we need to: Method #3. asking a developer to push pageview data to the Data Layer every time a visitor navigates from one page to another.
kapa.ai
kapa.aiā€¢3w ago
Let me check the knowledge base for you again. One moment! :books: ---------------------- I'm glad to hear that you've solved the Google Analytics tracking issue for your Wasp application. You're correct that for a Single Page Application (SPA), you need to manually track page views as the user navigates between different pages or routes. To implement this in your Wasp application, you can follow these steps: 1. First, make sure you have the Google Analytics script properly set up in your application. This is typically done in the head section of your HTML file or in a layout component. 2. In your main React component or router, you'll need to track page views when the route changes. Here's an example of how you might do this:
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function App() {
const location = useLocation();

useEffect(() => {
// Push to the dataLayer
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'pageview',
page: {
path: location.pathname,
title: document.title
}
});
}, [location]);

// Rest of your app code...
}
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

function App() {
const location = useLocation();

useEffect(() => {
// Push to the dataLayer
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'pageview',
page: {
path: location.pathname,
title: document.title
}
});
}, [location]);

// Rest of your app code...
}
This code uses the useLocation hook from react-router-dom to detect route changes, and then pushes a new pageview event to the dataLayer whenever the location changes. 3. Make sure to update your Google Analytics 4 configuration to create a custom event for these pageviews. In your Google Analytics 4 property: - Go to Configure > Events - Click "Create event" - Set up a new event that listens for the 'pageview' event you're pushing to the dataLayer For more detailed information on implementing Google Analytics in a Wasp application, you might want to check the OpenSaaS documentation on analytics (https://docs.opensaas.sh/guides/analytics/). While it doesn't specifically cover this SPA use case, it provides a good starting point for working with analytics in a Wasp context. Remember to test thoroughly to ensure that your pageviews are being tracked correctly as users navigate through your application. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ---------------------- Sorry @NEROX, I seem to be having a bit of trouble. Don't worry though, I've pinged my support and they will try to fix me up! :technologist: Perhaps try again in 15mins?
NEROX
NEROXOPā€¢3w ago
āœ… Solved: Very robust solution to ensure that all SPA pages are tracked: 1ļøāƒ£ Add to your @src/client/App.tsx this: // Add this new effect for page view tracking useEffect(() => { // Only track page views if analytics cookies are accepted // @ts-ignore if (window.CC?.acceptedCategories?.includes('analytics')) { window.dataLayer = window.dataLayer || []; window.dataLayer.push({ event: 'page_view', page: { path: location.pathname, title: document.title } }); } }, [location]); 2ļøāƒ£ Update your @src/client/components/cookie-consent/Config.ts to look like this (change G-XXXXXXXXXXXX by yours GAnalytics ID): import type { CookieConsentConfig } from 'vanilla-cookieconsent'; declare global { interface Window { dataLayer: any; } } const getConfig = (): CookieConsentConfig => ({ language: { default: 'en', translations: { en: { consentModal: { title: 'We use cookies', description: 'We use cookies and other tracking technologies to improve your browsing experience on our website, to analyze our website traffic, and to understand where our visitors are coming from.', acceptAllBtn: 'Accept all', acceptNecessaryBtn: 'Reject all', showPreferencesBtn: 'Manage preferences' }, preferencesModal: { title: 'Cookie preferences', acceptAllBtn: 'Accept all', acceptNecessaryBtn: 'Reject all', savePreferencesBtn: 'Save preferences', closeIconLabel: 'Close modal', sections: [ { title: 'Cookie Usage', description: 'We use cookies to ensure the basic functionalities of the website and to enhance your online experience.' }, { title: 'Strictly Necessary Cookies', description: 'These cookies are essential for the proper functioning of our website.', linkedCategory: 'necessary' }, { title: 'Analytics Cookies', description: 'These cookies collect information about how you use our website. All of the data is anonymized and cannot be used to identify you.', linkedCategory: 'analytics' } ] } } } }, guiOptions: { consentModal: { layout: 'box', position: 'bottom right', equalWeightButtons: true, flipButtons: false }, preferencesModal: { layout: 'box', position: 'right', equalWeightButtons: true, flipButtons: false } }, categories: { necessary: { enabled: true, readOnly: true }, analytics: { enabled: false, readOnly: false, services: { google_analytics: { label: 'Google Analytics', onAccept: () => { // Load Google Analytics when user accepts const script1 = document.createElement('script'); script1.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXXXX'; script1.async = true; const script2 = document.createElement('script'); script2.text = window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-XXXXXXXXXXXX'); ; document.head.appendChild(script1); document.head.appendChild(script2); }, onReject: () => { // Remove Google Analytics if user rejects window['ga-disable-G-22P31JZKV2'] = true; if (window.dataLayer) { window.dataLayer = []; } } } } } } }); export default getConfig; 3ļøāƒ£ Ready!
NEROX
NEROXOPā€¢3w ago
Will left here my example
miho
mihoā€¢3w ago
@NEROX you are the MVP, thank you for writing instructions when things work out for you šŸ™‚
NEROX
NEROXOPā€¢3w ago
I am currently stabilizing my income, once I have that calm I will start contributing much more. I love Wasp.ā¤ļø :boi:
martinsos
martinsosā€¢3w ago
Linked to this solution here https://github.com/wasp-lang/wasp/issues/1910#issuecomment-2441563995 so we don't forget it!
GitHub
Add "SEO" section in our docs Ā· Issue #1910 Ā· wasp-lang/wasp
Explain the state of SEO in Wasp, explain how they can set the meta tags for the landing page right now via head, and say also what we are planning for the future. Related convo: https://discord.co...
Want results from more Discord servers?
Add your server