From bc3d7b7ea8b5d9a65f353bd5fd6473e3c6425a67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl-Gerhard=20Lindesva=CC=88rd?= Date: Mon, 15 Dec 2025 10:14:40 +0100 Subject: [PATCH] docs: add guides --- .../content/docs/(tracking)/sdks/astro.mdx | 5 + .../content/docs/(tracking)/sdks/express.mdx | 5 + .../content/docs/(tracking)/sdks/index.mdx | 20 +- .../content/docs/(tracking)/sdks/kotlin.mdx | 4 + .../content/docs/(tracking)/sdks/nextjs.mdx | 5 + .../content/docs/(tracking)/sdks/python.mdx | 4 + .../docs/(tracking)/sdks/react-native.mdx | 5 + .../content/docs/(tracking)/sdks/swift.mdx | 4 + .../public/content/guides/astro-analytics.mdx | 171 ++++++++ .../content/guides/ecommerce-tracking.mdx | 200 +++++++++ .../content/guides/express-analytics.mdx | 219 ++++++++++ .../content/guides/kotlin-analytics.mdx | 266 ++++++++++++ .../guides/migrate-from-google-analytics.mdx | 232 +++++++++++ .../content/guides/migrate-from-mixpanel.mdx | 251 ++++++++++++ .../content/guides/nextjs-analytics.mdx | 255 ++++++++++++ .../content/guides/nodejs-analytics.mdx | 324 +++++++++++++++ .../content/guides/python-analytics.mdx | 208 ++++++++++ .../public/content/guides/react-analytics.mdx | 295 +++++++++++++ .../content/guides/react-native-analytics.mdx | 216 ++++++++++ .../public/content/guides/remix-analytics.mdx | 387 ++++++++++++++++++ .../public/content/guides/swift-analytics.mdx | 263 ++++++++++++ .../content/guides/track-custom-events.mdx | 226 ++++++++++ apps/public/content/guides/vue-analytics.mdx | 348 ++++++++++++++++ .../guides/website-analytics-setup.mdx | 178 ++++++++ apps/public/source.config.ts | 29 ++ .../app/(content)/guides/[guideSlug]/page.tsx | 212 ++++++++++ apps/public/src/app/(content)/guides/page.tsx | 79 ++++ apps/public/src/app/og/[...og]/route.tsx | 16 +- apps/public/src/components/footer.tsx | 1 + apps/public/src/components/guide-card.tsx | 67 +++ apps/public/src/lib/source.ts | 7 + 31 files changed, 4491 insertions(+), 11 deletions(-) create mode 100644 apps/public/content/guides/astro-analytics.mdx create mode 100644 apps/public/content/guides/ecommerce-tracking.mdx create mode 100644 apps/public/content/guides/express-analytics.mdx create mode 100644 apps/public/content/guides/kotlin-analytics.mdx create mode 100644 apps/public/content/guides/migrate-from-google-analytics.mdx create mode 100644 apps/public/content/guides/migrate-from-mixpanel.mdx create mode 100644 apps/public/content/guides/nextjs-analytics.mdx create mode 100644 apps/public/content/guides/nodejs-analytics.mdx create mode 100644 apps/public/content/guides/python-analytics.mdx create mode 100644 apps/public/content/guides/react-analytics.mdx create mode 100644 apps/public/content/guides/react-native-analytics.mdx create mode 100644 apps/public/content/guides/remix-analytics.mdx create mode 100644 apps/public/content/guides/swift-analytics.mdx create mode 100644 apps/public/content/guides/track-custom-events.mdx create mode 100644 apps/public/content/guides/vue-analytics.mdx create mode 100644 apps/public/content/guides/website-analytics-setup.mdx create mode 100644 apps/public/src/app/(content)/guides/[guideSlug]/page.tsx create mode 100644 apps/public/src/app/(content)/guides/page.tsx create mode 100644 apps/public/src/components/guide-card.tsx diff --git a/apps/public/content/docs/(tracking)/sdks/astro.mdx b/apps/public/content/docs/(tracking)/sdks/astro.mdx index 19534ed96..3826000de 100644 --- a/apps/public/content/docs/(tracking)/sdks/astro.mdx +++ b/apps/public/content/docs/(tracking)/sdks/astro.mdx @@ -3,9 +3,14 @@ title: Astro --- import { Step, Steps } from 'fumadocs-ui/components/steps'; +import { Callout } from 'fumadocs-ui/components/callout'; import CommonSdkConfig from '@/components/common-sdk-config.mdx'; import WebSdkConfig from '@/components/web-sdk-config.mdx'; + +Looking for a step-by-step tutorial? Check out the [Astro analytics guide](/guides/astro-analytics). + + ## Installation diff --git a/apps/public/content/docs/(tracking)/sdks/express.mdx b/apps/public/content/docs/(tracking)/sdks/express.mdx index 5d606acd3..4160dee2f 100644 --- a/apps/public/content/docs/(tracking)/sdks/express.mdx +++ b/apps/public/content/docs/(tracking)/sdks/express.mdx @@ -4,11 +4,16 @@ description: The Express middleware is a basic wrapper around Javascript SDK. It --- import Link from 'next/link'; +import { Callout } from 'fumadocs-ui/components/callout'; import { DeviceIdWarning } from '@/components/device-id-warning'; import { PersonalDataWarning } from '@/components/personal-data-warning'; import CommonSdkConfig from '@/components/common-sdk-config.mdx'; + +Looking for a step-by-step tutorial? Check out the [Express analytics guide](/guides/express-analytics). + + ## Installation ```bash diff --git a/apps/public/content/docs/(tracking)/sdks/index.mdx b/apps/public/content/docs/(tracking)/sdks/index.mdx index 5bffc11b4..37fad2c48 100644 --- a/apps/public/content/docs/(tracking)/sdks/index.mdx +++ b/apps/public/content/docs/(tracking)/sdks/index.mdx @@ -13,7 +13,7 @@ For most web projects, we recommend starting with one of these: - **[Script Tag](/docs/sdks/script)** - The quickest way to get started, no build step required - **[Web SDK](/docs/sdks/web)** - For TypeScript support and more control -- **[Next.js](/docs/sdks/nextjs)** - Optimized for Next.js applications +- **[Next.js](/docs/sdks/nextjs)** - Optimized for Next.js applications | [Setup guide](/guides/nextjs-analytics) ## Web & Browser SDKs @@ -38,12 +38,12 @@ For most web projects, we recommend starting with one of these: ### Frontend Frameworks -- **[Next.js](/docs/sdks/nextjs)** - Optimized for Next.js +- **[Next.js](/docs/sdks/nextjs)** - Optimized for Next.js | [Setup guide](/guides/nextjs-analytics) - ✅ Server and client side tracking - ✅ App Router support - ✅ Automatic route tracking -- **[Astro](/docs/sdks/astro)** - Astro framework integration +- **[Astro](/docs/sdks/astro)** - Astro framework integration | [Setup guide](/guides/astro-analytics) - ✅ Static and SSR support - ✅ Component-based tracking - ✅ Island architecture compatible @@ -51,10 +51,10 @@ For most web projects, we recommend starting with one of these: - **[React](/docs/sdks/react)** - React integration - 📝 Use Script Tag or Web SDK for now -- **[Vue](/docs/sdks/vue)** - Vue.js integration +- **[Vue](/docs/sdks/vue)** - Vue.js integration | [Setup guide](/guides/vue-analytics) - 📝 Use Script Tag or Web SDK for now -- **[Remix](/docs/sdks/remix)** - Remix framework integration +- **[Remix](/docs/sdks/remix)** - Remix framework integration | [Setup guide](/guides/remix-analytics) - 📝 Use Script Tag or Web SDK for now - **Svelte** - Svelte integration @@ -71,12 +71,12 @@ For most web projects, we recommend starting with one of these: ## Server-Side SDKs -- **[Node.js (Express)](/docs/sdks/express)** - Express.js middleware +- **[Node.js (Express)](/docs/sdks/express)** - Express.js middleware | [Setup guide](/guides/express-analytics) - ✅ Request tracking - ✅ Error tracking - ✅ Custom middleware support -- **[Python](/docs/sdks/python)** - Python SDK for server-side tracking +- **[Python](/docs/sdks/python)** - Python SDK for server-side tracking | [Setup guide](/guides/python-analytics) - ✅ Thread-safe - ✅ Async support - ✅ Django and Flask compatible @@ -93,18 +93,18 @@ For most web projects, we recommend starting with one of these: ## Mobile SDKs -- **[React Native](/docs/sdks/react-native)** - Cross-platform mobile analytics +- **[React Native](/docs/sdks/react-native)** - Cross-platform mobile analytics | [Setup guide](/guides/react-native-analytics) - ✅ iOS and Android support - ✅ Native performance - ✅ Offline support -- **[Swift](/docs/sdks/swift)** - Native iOS, macOS, tvOS, and watchOS SDK +- **[Swift](/docs/sdks/swift)** - Native iOS, macOS, tvOS, and watchOS SDK | [Setup guide](/guides/swift-analytics) - ✅ Apple platform support - ✅ Swift Package Manager - ✅ Thread-safe API - ✅ Automatic lifecycle tracking -- **[Kotlin / Android](/docs/sdks/kotlin)** - Native Android SDK +- **[Kotlin / Android](/docs/sdks/kotlin)** - Native Android SDK | [Setup guide](/guides/kotlin-analytics) - ✅ Android support - ✅ System information collection - ✅ Thread-safe API diff --git a/apps/public/content/docs/(tracking)/sdks/kotlin.mdx b/apps/public/content/docs/(tracking)/sdks/kotlin.mdx index 8ebf9723b..f3613cee2 100644 --- a/apps/public/content/docs/(tracking)/sdks/kotlin.mdx +++ b/apps/public/content/docs/(tracking)/sdks/kotlin.mdx @@ -9,6 +9,10 @@ import CommonSdkConfig from '@/components/common-sdk-config.mdx'; The OpenPanel Kotlin SDK allows you to track user behavior in your Kotlin applications. This guide provides instructions for installing and using the Kotlin SDK in your project. + +Looking for a step-by-step tutorial? Check out the [Kotlin analytics guide](/guides/kotlin-analytics). + + ## Installation diff --git a/apps/public/content/docs/(tracking)/sdks/nextjs.mdx b/apps/public/content/docs/(tracking)/sdks/nextjs.mdx index 9fec63d34..6f3173263 100644 --- a/apps/public/content/docs/(tracking)/sdks/nextjs.mdx +++ b/apps/public/content/docs/(tracking)/sdks/nextjs.mdx @@ -7,9 +7,14 @@ import { Step, Steps } from 'fumadocs-ui/components/steps'; import { DeviceIdWarning } from '@/components/device-id-warning'; import { PersonalDataWarning } from '@/components/personal-data-warning'; +import { Callout } from 'fumadocs-ui/components/callout'; import CommonSdkConfig from '@/components/common-sdk-config.mdx'; import WebSdkConfig from '@/components/web-sdk-config.mdx'; + +Looking for a step-by-step tutorial? Check out the [Next.js analytics guide](/guides/nextjs-analytics). + + ## Good to know Keep in mind that all tracking here happens on the client! diff --git a/apps/public/content/docs/(tracking)/sdks/python.mdx b/apps/public/content/docs/(tracking)/sdks/python.mdx index 07e2b57e5..af93a9479 100644 --- a/apps/public/content/docs/(tracking)/sdks/python.mdx +++ b/apps/public/content/docs/(tracking)/sdks/python.mdx @@ -8,6 +8,10 @@ import CommonSdkConfig from '@/components/common-sdk-config.mdx'; The OpenPanel Python SDK allows you to track user behavior in your Python applications. This guide provides instructions for installing and using the Python SDK in your project. + +Looking for a step-by-step tutorial? Check out the [Python analytics guide](/guides/python-analytics). + + ## Installation diff --git a/apps/public/content/docs/(tracking)/sdks/react-native.mdx b/apps/public/content/docs/(tracking)/sdks/react-native.mdx index fd79c5aa0..aadf014fc 100644 --- a/apps/public/content/docs/(tracking)/sdks/react-native.mdx +++ b/apps/public/content/docs/(tracking)/sdks/react-native.mdx @@ -8,8 +8,13 @@ import { Step, Steps } from 'fumadocs-ui/components/steps'; import { DeviceIdWarning } from '@/components/device-id-warning'; import { PersonalDataWarning } from '@/components/personal-data-warning'; +import { Callout } from 'fumadocs-ui/components/callout'; import CommonSdkConfig from '@/components/common-sdk-config.mdx'; + +Looking for a step-by-step tutorial? Check out the [React Native analytics guide](/guides/react-native-analytics). + + ## Installation diff --git a/apps/public/content/docs/(tracking)/sdks/swift.mdx b/apps/public/content/docs/(tracking)/sdks/swift.mdx index ecaa0bb4d..c6bc10ac1 100644 --- a/apps/public/content/docs/(tracking)/sdks/swift.mdx +++ b/apps/public/content/docs/(tracking)/sdks/swift.mdx @@ -9,6 +9,10 @@ import CommonSdkConfig from '@/components/common-sdk-config.mdx'; The OpenPanel Swift SDK allows you to integrate OpenPanel analytics into your iOS, macOS, tvOS, and watchOS applications. + +Looking for a step-by-step tutorial? Check out the [Swift analytics guide](/guides/swift-analytics). + + ## Features - Easy-to-use API for tracking events and user properties diff --git a/apps/public/content/guides/astro-analytics.mdx b/apps/public/content/guides/astro-analytics.mdx new file mode 100644 index 000000000..6c1dfe828 --- /dev/null +++ b/apps/public/content/guides/astro-analytics.mdx @@ -0,0 +1,171 @@ +--- +title: "How to add analytics to Astro" +description: "Add privacy-first analytics to your Astro site with OpenPanel. Track page views, custom events, and user behavior without cookies." +difficulty: beginner +timeToComplete: 5 +date: 2025-12-14 +cover: /content/cover-default.jpg +team: OpenPanel Team +steps: + - name: "Install the SDK" + anchor: "install" + - name: "Add the component to your layout" + anchor: "setup" + - name: "Track custom events" + anchor: "events" + - name: "Identify users" + anchor: "identify" + - name: "Verify your setup" + anchor: "verify" +--- + +# How to add analytics to Astro + +Adding analytics to your Astro site helps you understand how visitors interact with your content. This guide walks you through setting up OpenPanel to track page views, custom events, and user behavior in about five minutes. + +OpenPanel works well with Astro because it's a lightweight script that loads asynchronously and doesn't block your site's rendering. It tracks page views automatically across both static and server-rendered pages, and the component-based API fits naturally into Astro's architecture. + +## Prerequisites + +- An Astro project +- An OpenPanel account ([sign up free](https://dashboard.openpanel.dev/onboarding)) +- Your Client ID from the OpenPanel dashboard + +## Install the SDK [#install] + +Start by adding the OpenPanel Astro package to your project. This package provides Astro components that handle initialization and tracking. + +```bash +npm install @openpanel/astro +``` + +You can also use pnpm or yarn if that's your preference. + +## Add the component to your layout [#setup] + +The `OpenPanelComponent` initializes tracking and should be placed in your root layout so it loads on every page. Add it inside the `` tag to ensure it initializes before any user interactions. + +```astro +--- +import { OpenPanelComponent } from '@openpanel/astro'; +--- + + + + + + + + + +``` + +The `trackScreenViews` option automatically records a page view event whenever someone navigates to a new page. The `trackOutgoingLinks` option tracks when visitors click links that take them to external sites. Both are optional but recommended for most sites. + +You can also pass a `profileId` prop if you already know the user's identity at render time, and `globalProperties` to attach metadata to every event. + +## Track custom events [#events] + +Beyond automatic page views, you'll want to track specific interactions that matter to your business. OpenPanel exposes a global `op` function that you can call from any event handler. + +```astro + +``` + +The first argument is always `'track'`, the second is your event name, and the third is an optional object of properties you want to attach to the event. Keep event names consistent across your codebase, using snake_case is a good convention. + +For elements where you'd rather not write JavaScript, you can use data attributes instead. Any element with a `data-track` attribute will automatically fire an event when clicked. + +```astro + +``` + +Properties are pulled from any `data-*` attributes on the element. The `data-track` value becomes the event name, and other data attributes become event properties. + +### Tracking form submissions + +Forms are a common tracking target. You can fire an event in the `onsubmit` handler while still allowing the form to submit normally. + +```astro +
+ + +
+``` + +The `return true` ensures the form submission continues after the tracking call. + +## Identify users [#identify] + +When a user logs in or you otherwise learn their identity, you can associate their activity with a profile. The `IdentifyComponent` handles this declaratively. + +```astro +--- +import { IdentifyComponent } from '@openpanel/astro'; + +const user = await getCurrentUser(); +--- + + +``` + +Place this component on pages where the user is authenticated. Once identified, all subsequent events from that browser session will be linked to this profile until they clear their browser data or you explicitly clear the identity. + +### Setting global properties + +Sometimes you want to attach the same properties to every event, like an app version or environment. The `SetGlobalPropertiesComponent` lets you do this once rather than repeating it in every tracking call. + +```astro +--- +import { SetGlobalPropertiesComponent } from '@openpanel/astro'; +--- + + +``` + +These properties merge with any event-specific properties you pass to individual tracking calls. + +## Verify your setup [#verify] + +Open your Astro site in the browser and navigate between a few pages. Then open your [OpenPanel dashboard](https://dashboard.openpanel.dev) and check the Real-time view. You should see page view events appearing within seconds. + +If events aren't showing up, open your browser's developer console and look for errors. The most common issues are an incorrect Client ID or an ad blocker preventing the tracking script from loading. You can also check the Network tab to confirm requests are being sent to OpenPanel's servers. + +## Next steps + +The [Astro SDK reference](/docs/sdks/astro) covers additional configuration options like filtering events and customizing the CDN URL. If you're interested in running OpenPanel on your own infrastructure, the [self-hosting guide](/articles/how-to-self-host-openpanel) walks through the setup process. + + + +Yes. The OpenPanelComponent is a client-side script that hydrates independently of your island components. It will track interactions across your entire page regardless of which parts are hydrated. + + + +Yes. OpenPanel works with both static and server-rendered Astro sites. The tracking script runs in the browser regardless of how the page was rendered. + + + +No. OpenPanel uses cookieless tracking by default, which means you don't need cookie consent banners for basic analytics under most privacy regulations including GDPR. Read more about [cookieless analytics](/articles/cookieless-analytics). + + diff --git a/apps/public/content/guides/ecommerce-tracking.mdx b/apps/public/content/guides/ecommerce-tracking.mdx new file mode 100644 index 000000000..a3b86743c --- /dev/null +++ b/apps/public/content/guides/ecommerce-tracking.mdx @@ -0,0 +1,200 @@ +--- +title: "How to track e-commerce events and revenue" +description: "Track product views, cart activity, and purchases with OpenPanel to understand your e-commerce funnel and revenue." +difficulty: intermediate +timeToComplete: 10 +date: 2025-12-15 +cover: /content/cover-default.jpg +team: OpenPanel Team +steps: + - name: "Track product views" + anchor: "track-product-views" + - name: "Track cart activity" + anchor: "track-cart-activity" + - name: "Track purchases and revenue" + anchor: "track-purchases-and-revenue" + - name: "Verify your setup" + anchor: "verify" +--- + +# How to track e-commerce events and revenue + +E-commerce tracking gives you visibility into how users interact with your products and what drives purchases. By the end of this guide, you'll have product views, cart events, and revenue tracking working in your store. + +OpenPanel tracks revenue using a dedicated `revenue()` method that links payments to visitor sessions. This lets you see which traffic sources, campaigns, and pages generate the most revenue. You can track from your frontend for quick setup, or from your backend via webhooks for more accurate data. + +## Prerequisites + +- An OpenPanel account +- Your Client ID from the [dashboard](https://dashboard.openpanel.dev) +- The OpenPanel SDK installed in your project + +## Track product views [#track-product-views] + +Product view tracking helps you understand which items attract attention. When a user lands on a product page, fire an event with the product details so you can later analyze which products get viewed but not purchased. + +```tsx +function ProductPage({ product }) { + const op = useOpenPanel(); + + useEffect(() => { + op.track('product_viewed', { + product_id: product.id, + product_name: product.name, + product_category: product.category, + product_price: product.price, + currency: 'USD', + }); + }, [product.id]); + + return
{product.name}
; +} +``` + +Include consistent properties across all your product events. Using the same `product_id` format everywhere makes it easy to build reports that connect views to purchases. + +## Track cart activity [#track-cart-activity] + +Cart events reveal where users drop off in the purchase process. Track both additions and removals to understand cart abandonment patterns. + +When a user adds an item, capture the product details along with the current cart value. This gives you context about order sizes at different stages of the funnel. + +```tsx +function ProductCard({ product, cart }) { + const op = useOpenPanel(); + + const handleAddToCart = () => { + op.track('product_added_to_cart', { + product_id: product.id, + product_name: product.name, + product_price: product.price, + quantity: 1, + cart_value: cart.total + product.price, + currency: 'USD', + }); + + addToCart(product); + }; + + return ( + + ); +} +``` + +Track removals the same way. The symmetry between add and remove events makes it straightforward to calculate net cart changes. + +```tsx +const handleRemoveFromCart = (item) => { + op.track('product_removed_from_cart', { + product_id: item.id, + product_name: item.name, + product_price: item.price, + quantity: item.quantity, + currency: 'USD', + }); + + removeFromCart(item); +}; +``` + +## Track purchases and revenue [#track-purchases-and-revenue] + +Revenue tracking is where e-commerce analytics becomes actionable. OpenPanel provides dedicated methods for revenue that link payments back to visitor sessions, so you can see which traffic sources generate the most value. + +For frontend tracking, use `pendingRevenue()` before redirecting to checkout, then `flushRevenue()` on your success page. This approach works well when you want to get started quickly without backend changes. + +```tsx +async function handleCheckout(cart) { + const op = useOpenPanel(); + + op.pendingRevenue(cart.total, { + order_items: cart.items.length, + currency: 'USD', + }); + + window.location.href = await createCheckoutUrl(cart); +} +``` + +On your success page, flush the pending revenue to send it to OpenPanel. + +```tsx +function SuccessPage() { + const op = useOpenPanel(); + + useEffect(() => { + op.flushRevenue(); + }, []); + + return
Thank you for your purchase!
; +} +``` + +For more accurate tracking, handle revenue in your backend webhook. This ensures you only record completed payments. Pass the visitor's `deviceId` when creating the checkout so you can link the payment back to their session. + +```tsx +// Frontend: include deviceId when starting checkout +const deviceId = await op.fetchDeviceId(); + +const response = await fetch('/api/checkout', { + method: 'POST', + body: JSON.stringify({ + deviceId, + items: cart.items, + }), +}); +``` + +In your webhook handler, use that `deviceId` to attribute the revenue. + +```javascript +// Backend: webhook handler +export async function POST(req) { + const event = await req.json(); + + if (event.type === 'checkout.session.completed') { + const session = event.data.object; + + op.revenue(session.amount_total, { + deviceId: session.metadata.deviceId, + }); + } + + return Response.json({ received: true }); +} +``` + +If your users are logged in, you can use `profileId` instead of `deviceId`. This simplifies the flow since you don't need to capture the device ID during checkout. + +## Verify your setup [#verify] + +Open your OpenPanel dashboard and trigger a few events manually. Add a product to your cart, then check the live events view to confirm the events are arriving with the correct properties. + +For revenue tracking, you can test with a small transaction or use your payment provider's test mode. Look for your revenue events in the dashboard and verify the amounts match what you expect. + +If events aren't appearing, check that your Client ID is correct and that ad blockers aren't interfering. The browser's network tab can help you confirm requests are being sent. + +## Next steps + +Once you have basic e-commerce tracking working, you can build purchase funnels to visualize conversion rates at each step. The [revenue tracking documentation](/docs/revenue-tracking) covers advanced patterns like subscription tracking and refunds. For a deeper understanding of attribution, read about how OpenPanel's [cookieless tracking](/articles/cookieless-analytics) works. + +To learn more about tracking custom events in general, check out the [track custom events guide](/guides/track-custom-events) which covers event structure, properties, and common patterns. + + + +Yes. Once you track revenue using the `revenue()` or `flushRevenue()` methods, OpenPanel calculates totals, averages, and breakdowns by source automatically. You can view these in the revenue section of your dashboard. + + + +Backend tracking via webhooks is more accurate since it only records completed payments. Frontend tracking is faster to implement but may count abandoned checkouts. For production stores, backend tracking is recommended. + + + +Yes. Track subscription events like any other revenue event, including properties for plan name and billing period. The revenue tracking documentation covers subscription-specific patterns in detail. + + + +Track refunds as separate events with the refund amount. This lets you calculate net revenue by subtracting refunds from gross revenue in your reports. + + diff --git a/apps/public/content/guides/express-analytics.mdx b/apps/public/content/guides/express-analytics.mdx new file mode 100644 index 000000000..93ed10c17 --- /dev/null +++ b/apps/public/content/guides/express-analytics.mdx @@ -0,0 +1,219 @@ +--- +title: "How to add analytics to Express" +description: "Add server-side analytics to your Express application with OpenPanel middleware. Track API requests, user actions, and custom events." +difficulty: beginner +timeToComplete: 8 +date: 2025-12-15 +cover: /content/cover-default.jpg +team: OpenPanel Team +steps: + - name: "Install the SDK" + anchor: "install" + - name: "Add the middleware" + anchor: "middleware" + - name: "Track events" + anchor: "events" + - name: "Identify users" + anchor: "identify" + - name: "Verify your setup" + anchor: "verify" +--- + +# How to add analytics to Express + +Server-side analytics gives you reliable event tracking that cannot be blocked by ad blockers or browser extensions. The OpenPanel Express middleware wraps the JavaScript SDK and attaches it to every request, making it simple to track events throughout your application. + +OpenPanel is an open-source alternative to Mixpanel and Amplitude. You get powerful analytics with full control over your data, and you can [self-host](/articles/how-to-self-host-openpanel) if privacy requirements demand it. + +## Prerequisites + +- An Express application +- An OpenPanel account ([sign up free](https://dashboard.openpanel.dev/onboarding)) +- Your Client ID and Client Secret from the OpenPanel dashboard + +Server-side tracking requires a `clientSecret` for authentication since the server cannot rely on browser CORS headers to verify the request origin. + +## Install the SDK [#install] + +The Express SDK is a lightweight middleware that creates an OpenPanel instance for each request. Install it with npm (pnpm and yarn work too). + +```bash +npm install @openpanel/express +``` + +## Add the middleware [#middleware] + +The middleware attaches the OpenPanel SDK to every request as `req.op`. Add it early in your middleware chain so it is available in all your route handlers. + +```ts +import express from 'express'; +import createOpenpanelMiddleware from '@openpanel/express'; + +const app = express(); + +app.use(express.json()); + +app.use( + createOpenpanelMiddleware({ + clientId: 'YOUR_CLIENT_ID', + clientSecret: 'YOUR_CLIENT_SECRET', + }) +); + +app.listen(3000, () => { + console.log('Server running on http://localhost:3000'); +}); +``` + +You should store your credentials in environment variables rather than hardcoding them. This keeps secrets out of version control and makes it easy to use different credentials in development and production. + +```ts +app.use( + createOpenpanelMiddleware({ + clientId: process.env.OPENPANEL_CLIENT_ID!, + clientSecret: process.env.OPENPANEL_CLIENT_SECRET!, + }) +); +``` + +The middleware also forwards the client IP address and user-agent from incoming requests, so geographic and device data will be accurate even though events originate from your server. + +## Track events [#events] + +Once the middleware is in place, you can track events in any route handler by calling `req.op.track()`. The first argument is the event name and the second is an object of properties you want to attach. + +```ts +app.post('/signup', async (req, res) => { + const { email, name } = req.body; + + req.op.track('user_signed_up', { + email, + name, + source: 'website', + }); + + const user = await createUser({ email, name }); + res.json({ success: true, user }); +}); +``` + +You can track any event that matters to your business. Common examples include form submissions, purchases, feature usage, and API errors. + +```ts +app.post('/contact', async (req, res) => { + const { email, message } = req.body; + + req.op.track('contact_form_submitted', { + email, + message_length: message.length, + }); + + await sendContactEmail(email, message); + res.json({ success: true }); +}); +``` + +### Automatic request tracking + +The middleware can automatically track every request if you provide a `trackRequest` function. This is useful for monitoring API usage without manually adding tracking calls to each route. + +```ts +app.use( + createOpenpanelMiddleware({ + clientId: process.env.OPENPANEL_CLIENT_ID!, + clientSecret: process.env.OPENPANEL_CLIENT_SECRET!, + trackRequest: (url) => url.startsWith('/api/'), + }) +); +``` + +When `trackRequest` returns true, the middleware sends a `request` event with the URL, method, and query parameters. + +## Identify users [#identify] + +To associate events with specific users, use the `getProfileId` option in the middleware configuration. This function receives the request object and should return the user's ID. + +```ts +app.use( + createOpenpanelMiddleware({ + clientId: process.env.OPENPANEL_CLIENT_ID!, + clientSecret: process.env.OPENPANEL_CLIENT_SECRET!, + getProfileId: (req) => req.user?.id, + }) +); +``` + +You can also send user profile data with `req.op.identify()`. This updates the user's profile in OpenPanel with properties like name, email, and any custom attributes. + +```ts +app.post('/login', async (req, res) => { + const { email, password } = req.body; + const user = await authenticateUser(email, password); + + req.op.identify({ + profileId: user.id, + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + properties: { + plan: user.plan, + signupDate: user.createdAt, + }, + }); + + req.op.track('user_logged_in', { method: 'email' }); + res.json({ success: true, user }); +}); +``` + +### Increment profile properties + +If you want to track cumulative values on a user profile, like login count or total purchases, use the `increment` method. + +```ts +req.op.increment({ + profileId: user.id, + property: 'login_count', + value: 1, +}); +``` + +## Verify your setup [#verify] + +Start your Express server and trigger a few events by making requests to your endpoints. Open the [OpenPanel dashboard](https://dashboard.openpanel.dev) and navigate to the Real-time view to see events as they arrive. + +If events are not appearing, check your server logs for error responses from OpenPanel. Verify that both `clientId` and `clientSecret` are correct and that the middleware is added before your routes. + +## TypeScript support + +The Express SDK automatically extends the `Request` interface to include `req.op`. If your TypeScript configuration does not pick this up, you can extend the interface manually in a declaration file. + +```ts +import { OpenPanel } from '@openpanel/express'; + +declare global { + namespace Express { + export interface Request { + op: OpenPanel; + } + } +} +``` + +## Next steps + +The [Express SDK reference](/docs/sdks/express) covers all available options and methods. If you are using a different Node.js framework, the [Node.js tracking guide](/guides/nodejs-analytics) shows how to use the base SDK directly. For comparing OpenPanel to other analytics tools, see the [Mixpanel alternative](/compare/mixpanel-alternative) page. + + + +Server-side tracking requires authentication because requests come from your server, not a browser with CORS restrictions. The clientSecret ensures events are properly authenticated and prevents unauthorized tracking from other sources. + + + +Yes. The OpenPanel SDK sends events asynchronously by default. Events are queued and dispatched in the background, so tracking calls will not block your route handlers or slow down response times. + + + +Yes. OpenPanel is designed for GDPR compliance with cookieless tracking and data minimization. Server-side tracking gives you full control over what data you collect. With self-hosting, you eliminate international data transfer concerns entirely. + + diff --git a/apps/public/content/guides/kotlin-analytics.mdx b/apps/public/content/guides/kotlin-analytics.mdx new file mode 100644 index 000000000..b0d319d94 --- /dev/null +++ b/apps/public/content/guides/kotlin-analytics.mdx @@ -0,0 +1,266 @@ +--- +title: "How to add analytics to Android apps" +description: "Add privacy-first analytics to Android applications using OpenPanel's Kotlin SDK. Track events, identify users, and analyze behavior." +type: guide +difficulty: intermediate +timeToComplete: 10 +date: 2025-12-15 +lastUpdated: 2025-12-15 +steps: + - name: "Add the dependency" + anchor: "install" + - name: "Initialize OpenPanel" + anchor: "setup" + - name: "Track events" + anchor: "events" + - name: "Identify users" + anchor: "identify" + - name: "Track screen views" + anchor: "screenviews" + - name: "Verify your setup" + anchor: "verify" +--- + +# How to add analytics to Android apps + +This guide walks you through adding OpenPanel analytics to an Android application using the Kotlin SDK. You'll learn how to track events, identify users, and monitor screen views across your app. + +OpenPanel works well for Android apps because it provides a lightweight, privacy-focused SDK that handles offline queuing and automatic system information collection. Unlike web SDKs, native apps require a client secret for authentication since CORS headers aren't available. + +## Prerequisites + +- An Android project (minSdkVersion 21+) +- An OpenPanel account +- Your Client ID and Client Secret from the [dashboard](https://dashboard.openpanel.dev) + +## Add the dependency [#install] + +Start by adding the OpenPanel SDK to your app's `build.gradle.kts` file. The SDK is available through standard Gradle dependency management. + +```kotlin +dependencies { + implementation("dev.openpanel:openpanel:0.0.1") +} +``` + +The Kotlin SDK is currently in development, so check the [GitHub repository](https://github.com/Openpanel-dev/kotlin-sdk) for the latest version number before adding it to your project. + +## Initialize OpenPanel [#setup] + +Before you can track events, you need to initialize OpenPanel in your Application class. This ensures the SDK is available throughout your app and can properly manage its lifecycle. + +```kotlin +import android.app.Application +import dev.openpanel.OpenPanel + +class MyApplication : Application() { + override fun onCreate() { + super.onCreate() + + OpenPanel.create( + context = this, + options = OpenPanel.Options( + clientId = "YOUR_CLIENT_ID", + clientSecret = "YOUR_CLIENT_SECRET" + ) + ) + } +} +``` + +You also need to register your Application class in the Android manifest so the system knows to use it. + +```xml + + +``` + +If you're using dependency injection with Hilt or Dagger, you can provide OpenPanel as a singleton instead. This approach integrates better with modern Android architecture patterns. + +```kotlin +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import dev.openpanel.OpenPanel +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object AppModule { + @Provides + @Singleton + fun provideOpenPanel(@ApplicationContext context: Context): OpenPanel { + return OpenPanel.create( + context, + OpenPanel.Options( + clientId = "YOUR_CLIENT_ID", + clientSecret = "YOUR_CLIENT_SECRET" + ) + ) + } +} +``` + +## Track events [#events] + +Once OpenPanel is initialized, you can track events anywhere in your app by getting the SDK instance and calling the `track` method. Each event has a name and an optional map of properties. + +```kotlin +import dev.openpanel.OpenPanel + +class MainActivity : AppCompatActivity() { + private lateinit var op: OpenPanel + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + + op = OpenPanel.getInstance(this) + + findViewById + ); +} +``` + +For server-side tracking, you'll need to create an SDK instance with your client secret. This is useful for tracking events in API routes, webhooks, or server actions. + +```tsx +// lib/op.ts +import { OpenPanel } from '@openpanel/nextjs'; + +export const op = new OpenPanel({ + clientId: 'your-client-id', + clientSecret: 'your-client-secret', +}); +``` + +Never expose your client secret on the client side. Keep it in server-only code. + +With the instance created, you can track events from anywhere on the server. + +```tsx +// app/api/webhook/route.ts +import { op } from '@/lib/op'; + +export async function POST(request: Request) { + const data = await request.json(); + + op.track('webhook_received', { + source: data.source, + event_type: data.type, + }); + + return Response.json({ success: true }); +} +``` + +If you're running on Vercel or another serverless platform, wrap your tracking calls with `waitUntil` to ensure events are sent before the function terminates. + +## Identify users [#identify] + +Anonymous tracking is useful, but identifying users unlocks more valuable insights. You can track behavior across sessions, segment users by properties, and build cohort analyses. + +In client components, call `identify` after a user logs in or when you have their information available. + +```tsx +'use client'; + +import { useOpenPanel } from '@openpanel/nextjs'; +import { useEffect } from 'react'; + +export function UserProfile({ user }) { + const op = useOpenPanel(); + + useEffect(() => { + op.identify({ + profileId: user.id, + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + properties: { + plan: user.plan, + }, + }); + }, [user]); + + return
Welcome, {user.firstName}!
; +} +``` + +If you already have user data on the server, the `IdentifyComponent` is a cleaner approach. It renders nothing visible but handles identification when the component mounts. + +```tsx +// app/dashboard/layout.tsx +import { IdentifyComponent } from '@openpanel/nextjs'; + +export default async function DashboardLayout({ children }) { + const user = await getCurrentUser(); + + return ( + <> + + {children} + + ); +} +``` + +## Verify your setup [#verify] + +Open your Next.js app in the browser and navigate between a few pages. Then open your [OpenPanel dashboard](https://dashboard.openpanel.dev) and check the real-time view. You should see page view events appearing within seconds. + +If events aren't showing up, check the browser console for errors. The most common issues are an incorrect client ID or ad blockers intercepting requests. You can solve the ad blocker problem by proxying events through your own server. + +To set up a proxy, create a catch-all API route that forwards requests to OpenPanel. + +```tsx +// app/api/[...op]/route.ts +import { createRouteHandler } from '@openpanel/nextjs/server'; + +export const { GET, POST } = createRouteHandler(); +``` + +Then update your OpenPanelComponent to use the proxy endpoint. + +```tsx + +``` + +This routes all tracking requests through your domain, making them invisible to browser extensions that block third-party analytics. + +## Next steps + +The [Next.js SDK reference](/docs/sdks/nextjs) covers additional features like global properties, event filtering, and incrementing user properties. If you're interested in understanding how OpenPanel handles privacy, read our article on [cookieless analytics](/articles/cookieless-analytics). + + + +Yes. The setup is nearly identical for both. Add OpenPanelComponent to your root layout for App Router or to _app.tsx for Pages Router. All tracking features work the same way regardless of which router you use. + + + +No. OpenPanel uses cookieless tracking by default. This means you don't need cookie consent banners for basic analytics under most privacy regulations, including GDPR and PECR. + + + +Yes. OpenPanel is designed for GDPR compliance with cookieless tracking, data minimization, and full support for data subject rights. With self-hosting, you also eliminate international data transfer concerns entirely. + + + +Set up the proxy route handler as shown in the verification section. This routes tracking requests through your own domain, which ad blockers don't typically block. + + diff --git a/apps/public/content/guides/nodejs-analytics.mdx b/apps/public/content/guides/nodejs-analytics.mdx new file mode 100644 index 000000000..8541dd016 --- /dev/null +++ b/apps/public/content/guides/nodejs-analytics.mdx @@ -0,0 +1,324 @@ +--- +title: "How to Track Events with Node.js" +description: "Add server-side analytics to your Node.js application. Track events, identify users, and analyze behavior with OpenPanel's JavaScript SDK." +difficulty: beginner +timeToComplete: 7 +date: 2025-12-14 +cover: /content/cover-default.jpg +team: OpenPanel Team +steps: + - name: "Install the SDK" + anchor: "install" + - name: "Initialize OpenPanel" + anchor: "setup" + - name: "Track events" + anchor: "events" + - name: "Identify users" + anchor: "identify" + - name: "Verify your setup" + anchor: "verify" +--- + +## Introduction + +Server-side analytics gives you complete control over what data you track and ensures events are never blocked by ad blockers. OpenPanel's JavaScript SDK works perfectly in Node.js environments, allowing you to track events from your backend, API routes, and background jobs. + +OpenPanel is an open-source alternative to Mixpanel and Amplitude, giving you powerful server-side analytics without compromising user privacy. + +## Prerequisites + +- Node.js project set up +- OpenPanel account ([sign up free](https://dashboard.openpanel.dev/onboarding)) +- Your Client ID and Client Secret from the OpenPanel dashboard + +> **Important:** Server-side tracking requires a `clientSecret` for authentication. + +## Step 1: Install the SDK + +Install the OpenPanel JavaScript SDK: + +```bash +npm install @openpanel/sdk +``` + +Or with pnpm: + +```bash +pnpm install @openpanel/sdk +``` + +## Step 2: Initialize OpenPanel + +Create an OpenPanel instance with your credentials: + +```js title="lib/op.js" +import { OpenPanel } from '@openpanel/sdk'; + +export const op = new OpenPanel({ + clientId: 'YOUR_CLIENT_ID', + clientSecret: 'YOUR_CLIENT_SECRET', // Required for server-side +}); +``` + +> **Security Note:** Never expose your `clientSecret` in client-side code. Use environment variables in production. + +### Using environment variables + +```js title="lib/op.js" +import { OpenPanel } from '@openpanel/sdk'; + +export const op = new OpenPanel({ + clientId: process.env.OPENPANEL_CLIENT_ID, + clientSecret: process.env.OPENPANEL_CLIENT_SECRET, +}); +``` + +```bash title=".env" +OPENPANEL_CLIENT_ID=your-client-id +OPENPANEL_CLIENT_SECRET=your-client-secret +``` + +## Step 3: Track events + +Track events throughout your Node.js application: + +```js title="routes/api/signup.js" +import { op } from '@/lib/op'; + +export async function POST(request) { + const { email, name } = await request.json(); + + // Track signup event + op.track('user_signed_up', { + email, + name, + source: 'website', + }); + + // Your signup logic + const user = await createUser({ email, name }); + + return Response.json({ success: true, user }); +} +``` + +### Track API requests + +```js title="middleware/analytics.js" +import { op } from '@/lib/op'; + +export function analyticsMiddleware(req, res, next) { + // Track API request + op.track('api_request', { + method: req.method, + path: req.path, + status_code: res.statusCode, + }); + + next(); +} +``` + +### Track background jobs + +```js title="jobs/send-email.js" +import { op } from '@/lib/op'; + +export async function sendEmailJob(userId, emailData) { + try { + await sendEmail(emailData); + + op.track('email_sent', { + user_id: userId, + email_type: emailData.type, + success: true, + }); + } catch (error) { + op.track('email_failed', { + user_id: userId, + email_type: emailData.type, + error: error.message, + }); + } +} +``` + +## Step 4: Identify users + +To identify users and track their behavior: + +```js title="routes/api/login.js" +import { op } from '@/lib/op'; + +export async function POST(request) { + const { email, password } = await request.json(); + + const user = await authenticateUser(email, password); + + // Identify the user + op.identify({ + profileId: user.id, + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + properties: { + plan: user.plan, + signupDate: user.createdAt, + }, + }); + + // Track login event + op.track('user_logged_in', { + user_id: user.id, + method: 'email', + }); + + return Response.json({ success: true, user }); +} +``` + +### Track user actions + +```js title="routes/api/purchase.js" +import { op } from '@/lib/op'; + +export async function POST(request) { + const { userId, productId, amount } = await request.json(); + + // Track purchase event + op.track('purchase_completed', { + user_id: userId, + product_id: productId, + amount, + currency: 'USD', + }, { + profileId: userId, // Associate event with user + }); + + // Your purchase logic + await processPurchase(userId, productId, amount); + + return Response.json({ success: true }); +} +``` + +## Verify your setup + +1. Make requests to your API endpoints that trigger events +2. Run background jobs that track events +3. Open your [OpenPanel dashboard](https://dashboard.openpanel.dev) +4. Check the Real-time view to see events coming in + +**Not seeing events?** + +- Check server logs for errors +- Verify your Client ID and Client Secret are correct +- Ensure `clientSecret` is provided (required for server-side) +- Check network requests in your server logs + +## Common Patterns + +### Track webhook events + +```js title="routes/api/webhook.js" +import { op } from '@/lib/op'; + +export async function POST(request) { + const payload = await request.json(); + + op.track('webhook_received', { + source: payload.source, + event_type: payload.type, + timestamp: new Date().toISOString(), + }); + + // Process webhook + await processWebhook(payload); + + return Response.json({ success: true }); +} +``` + +### Set global properties + +```js title="lib/op.js" +import { OpenPanel } from '@openpanel/sdk'; + +export const op = new OpenPanel({ + clientId: process.env.OPENPANEL_CLIENT_ID, + clientSecret: process.env.OPENPANEL_CLIENT_SECRET, + globalProperties: { + app_version: process.env.APP_VERSION, + environment: process.env.NODE_ENV, + server_region: process.env.SERVER_REGION, + }, +}); +``` + +### Track errors + +```js title="middleware/error-handler.js" +import { op } from '@/lib/op'; + +export function errorHandler(err, req, res, next) { + // Track error event + op.track('error_occurred', { + error_message: err.message, + error_stack: err.stack, + path: req.path, + method: req.method, + }); + + // Your error handling logic + res.status(500).json({ error: 'Internal server error' }); +} +``` + +### Increment user properties + +```js +import { op } from '@/lib/op'; + +// Increment login count +op.increment({ + profileId: user.id, + property: 'login_count', + value: 1, +}); +``` + +## Serverless & Vercel + +If you're using serverless functions (like Vercel), use `waitUntil` to ensure events are tracked before the function completes: + +```js title="api/track.js" +import { waitUntil } from '@vercel/functions'; +import { op } from '@/lib/op'; + +export default async function handler(req, res) { + // Returns response immediately while keeping function alive + waitUntil(op.track('important_event', { foo: 'bar' })); + + return res.status(200).json({ success: true }); +} +``` + +## Next Steps + +- [Full JavaScript SDK reference](/docs/sdks/javascript) +- [How to Add Analytics to Express](/guides/express-analytics) +- [Compare OpenPanel to Mixpanel](/compare/mixpanel-alternative) + +## FAQ + +### Why do I need a clientSecret for server-side tracking? + +Server-side tracking requires authentication since we can't use CORS headers. The `clientSecret` ensures your events are properly authenticated and prevents unauthorized tracking. + +### Can I track events asynchronously? + +Yes! The OpenPanel SDK tracks events asynchronously by default. Events are queued and sent in the background, so they won't block your application. + +### Is OpenPanel GDPR compliant? + +Yes! OpenPanel is designed with privacy in mind. Server-side tracking gives you complete control over what data you collect. Check out our [cookieless analytics guide](/articles/cookieless-analytics) for more information. diff --git a/apps/public/content/guides/python-analytics.mdx b/apps/public/content/guides/python-analytics.mdx new file mode 100644 index 000000000..013d4bd4d --- /dev/null +++ b/apps/public/content/guides/python-analytics.mdx @@ -0,0 +1,208 @@ +--- +title: "How to add analytics to Python" +description: "Add server-side analytics to your Python application with OpenPanel's Python SDK." +type: guide +difficulty: beginner +timeToComplete: 7 +date: 2025-12-15 +lastUpdated: 2025-12-15 +steps: + - name: "Install the SDK" + anchor: "install" + - name: "Initialize OpenPanel" + anchor: "initialize" + - name: "Track events" + anchor: "track-events" + - name: "Identify users" + anchor: "identify-users" + - name: "Verify your setup" + anchor: "verify" +--- + +# How to add analytics to Python + +This guide walks you through adding server-side analytics to any Python application. You'll install the OpenPanel SDK, configure it with your credentials, track custom events, and identify users. + +Server-side tracking gives you complete control over what data you collect and ensures events are never blocked by browser extensions or ad blockers. The Python SDK works with Django, Flask, FastAPI, and any other Python framework or script. + +## Prerequisites + +- A Python project +- An OpenPanel account +- Your Client ID and Client Secret from the dashboard + +## Install the SDK [#install] + +Start by installing the OpenPanel package from PyPI. + +```bash +pip install openpanel +``` + +If you're using Poetry, you can run `poetry add openpanel` instead. + +## Initialize OpenPanel [#initialize] + +Create a shared module for your OpenPanel instance. This approach lets you import the same configured instance throughout your application. + +```python +# lib/op.py +import os +from openpanel import OpenPanel + +op = OpenPanel( + client_id=os.getenv("OPENPANEL_CLIENT_ID"), + client_secret=os.getenv("OPENPANEL_CLIENT_SECRET") +) +``` + +Server-side tracking requires both a client ID and client secret for authentication. Add these to your environment variables. + +```bash +# .env +OPENPANEL_CLIENT_ID=your-client-id +OPENPANEL_CLIENT_SECRET=your-client-secret +``` + +You can also pass global properties during initialization. These properties are included with every event automatically. + +```python +op = OpenPanel( + client_id=os.getenv("OPENPANEL_CLIENT_ID"), + client_secret=os.getenv("OPENPANEL_CLIENT_SECRET"), + global_properties={ + "app_version": "1.0.0", + "environment": os.getenv("ENVIRONMENT", "production") + } +) +``` + +## Track events [#track-events] + +Use the `track` method to record events. The first argument is the event name, and the second is an optional dictionary of properties. + +```python +from lib.op import op + +# Track a simple event +op.track("button_clicked") + +# Track with properties +op.track("purchase_completed", { + "product_id": "123", + "price": 99.99, + "currency": "USD" +}) +``` + +When tracking events in request handlers, you'll typically pull data from the request and track it alongside your business logic. Here's an example in a Django view. + +```python +from lib.op import op + +def signup_view(request): + if request.method == 'POST': + email = request.POST.get('email') + name = request.POST.get('name') + + user = create_user(email, name) + + op.track("user_signed_up", { + "email": email, + "source": "website" + }) + + return JsonResponse({"success": True}) +``` + +The same pattern works in Flask and FastAPI. Import your OpenPanel instance and call `track` wherever you need to record an event. + +You can also track events from background tasks. This is useful for monitoring async jobs, email delivery, and scheduled tasks. + +```python +from celery import shared_task +from lib.op import op + +@shared_task +def send_email_task(user_id, email_type): + try: + send_email(user_id, email_type) + op.track("email_sent", { + "user_id": user_id, + "email_type": email_type + }) + except Exception as e: + op.track("email_failed", { + "user_id": user_id, + "error": str(e) + }) +``` + +## Identify users [#identify-users] + +The `identify` method associates a user profile with their ID. Call this after authentication to link subsequent events to that user. + +```python +from lib.op import op + +def login_view(request): + user = authenticate_user(request) + + op.identify(user.id, { + "firstName": user.first_name, + "lastName": user.last_name, + "email": user.email, + "tier": user.plan + }) + + op.track("user_logged_in", {"method": "email"}) + + return JsonResponse({"success": True}) +``` + +To track an event for a specific user without calling identify first, pass the `profile_id` parameter to the track method. + +```python +op.track("purchase_completed", { + "product_id": "123", + "amount": 99.99 +}, profile_id="user_456") +``` + +You can increment numeric properties on user profiles. This is useful for counters like login count or total purchases. + +```python +op.increment({ + "profile_id": "user_456", + "property": "login_count", + "value": 1 +}) +``` + +## Verify your setup [#verify] + +Run your Python application and trigger a few events. Open your [OpenPanel dashboard](https://dashboard.openpanel.dev) and navigate to the real-time view. You should see your events appearing within seconds. + +If events aren't showing up, check that your client ID and client secret are correct. Server-side tracking won't work without the client secret. Review your application logs for any error messages from the SDK. + +## Next steps + +The [Python SDK reference](/docs/sdks/python) covers additional configuration options like event filtering and disabling tracking. If you're also tracking client-side events, you might want to read about [cookieless analytics](/articles/cookieless-analytics) to understand how OpenPanel handles privacy without cookies. + + + +Server-side tracking can't rely on browser-based authentication like CORS headers. The client secret authenticates your requests and prevents unauthorized parties from sending events to your project. + + + +Yes. The OpenPanel Python SDK is thread-safe and you can use a single instance across multiple threads. This makes it safe to use in threaded web servers and background task workers. + + + +Yes. The SDK tracks events asynchronously by default, so it won't block your async request handlers. Events are queued and sent in the background. + + + +Yes. OpenPanel is designed for GDPR compliance with cookieless tracking, data minimization, and support for data subject rights. Server-side tracking gives you complete control over what data you collect. + + diff --git a/apps/public/content/guides/react-analytics.mdx b/apps/public/content/guides/react-analytics.mdx new file mode 100644 index 000000000..db6dcc0f5 --- /dev/null +++ b/apps/public/content/guides/react-analytics.mdx @@ -0,0 +1,295 @@ +--- +title: "How to add analytics to React" +description: "Add privacy-first analytics to your React application with OpenPanel's Web SDK. Track page views, custom events, and user behavior." +difficulty: beginner +timeToComplete: 8 +date: 2025-12-14 +cover: /content/cover-default.jpg +team: OpenPanel Team +steps: + - name: "Install the SDK" + anchor: "install" + - name: "Create an OpenPanel instance" + anchor: "setup" + - name: "Track page views" + anchor: "pageviews" + - name: "Track custom events" + anchor: "events" + - name: "Identify users" + anchor: "identify" + - name: "Verify your setup" + anchor: "verify" +--- + +Adding analytics to your React application helps you understand how users interact with your app. OpenPanel's Web SDK works seamlessly with React and React Router, giving you page view tracking, custom events, and user identification without the complexity of dedicated React bindings. + +OpenPanel is an open-source alternative to Mixpanel and Google Analytics. It provides powerful insights while respecting user privacy through cookieless tracking by default. + +## Prerequisites + +- A React project (Create React App, Vite, or similar) +- An OpenPanel account ([sign up free](https://dashboard.openpanel.dev/onboarding)) +- Your Client ID from the OpenPanel dashboard + +## Install the SDK [#install] + +The OpenPanel Web SDK is a lightweight package that works in any JavaScript environment, including React. Install it using npm, and pnpm or yarn work the same way. + +```bash +npm install @openpanel/web +``` + +## Create an OpenPanel instance [#setup] + +Create a dedicated file for your OpenPanel instance. This keeps your analytics configuration centralized and makes the instance easy to import throughout your application. + +```ts title="src/lib/op.ts" +import { OpenPanel } from '@openpanel/web'; + +export const op = new OpenPanel({ + clientId: 'YOUR_CLIENT_ID', + trackScreenViews: true, + trackOutgoingLinks: true, + trackAttributes: true, +}); +``` + +The `trackScreenViews` option automatically tracks page views when the URL changes. This works with React Router, TanStack Router, and other client-side routing solutions. The `trackAttributes` option enables declarative tracking using `data-track` attributes on HTML elements. + +### Using environment variables + +For production applications, store your Client ID in environment variables. Vite and Create React App handle these differently. + +```ts title="src/lib/op.ts (Vite)" +import { OpenPanel } from '@openpanel/web'; + +export const op = new OpenPanel({ + clientId: import.meta.env.VITE_OPENPANEL_CLIENT_ID, + trackScreenViews: true, + trackOutgoingLinks: true, + trackAttributes: true, +}); +``` + +```ts title="src/lib/op.ts (Create React App)" +import { OpenPanel } from '@openpanel/web'; + +export const op = new OpenPanel({ + clientId: process.env.REACT_APP_OPENPANEL_CLIENT_ID, + trackScreenViews: true, + trackOutgoingLinks: true, + trackAttributes: true, +}); +``` + +### Initialize on app load + +Import the OpenPanel instance in your app's entry point to ensure it initializes when your application loads. This is all you need to start tracking page views automatically. + +```tsx title="src/main.tsx" +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import './lib/op'; // Initialize OpenPanel + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); +``` + +## Track page views [#pageviews] + +With `trackScreenViews: true`, OpenPanel automatically tracks page views when the browser's URL changes. This works out of the box with React Router and other routing libraries that use the History API. + +If you need to track page views manually or want more control over the data sent with each view, you can create a component that listens to route changes. + +```tsx title="src/components/PageTracker.tsx" +import { useEffect } from 'react'; +import { useLocation } from 'react-router-dom'; +import { op } from '../lib/op'; + +export function PageTracker() { + const location = useLocation(); + + useEffect(() => { + op.track('screen_view', { + path: location.pathname, + search: location.search, + }); + }, [location.pathname, location.search]); + + return null; +} +``` + +Add this component near the root of your application, inside your router context. Note that if you use this approach, you should set `trackScreenViews: false` in your OpenPanel configuration to avoid duplicate tracking. + +## Track custom events [#events] + +Import the OpenPanel instance wherever you need to track events. The `track` method accepts an event name and an optional properties object. + +```tsx title="src/components/SignupButton.tsx" +import { op } from '../lib/op'; + +export function SignupButton() { + const handleClick = () => { + op.track('button_clicked', { + button_name: 'signup', + button_location: 'hero', + }); + }; + + return ( + + ); +} +``` + +### Track form submissions + +Form tracking helps you understand conversion rates and identify where users drop off. + +```tsx title="src/components/ContactForm.tsx" +import { useState } from 'react'; +import { op } from '../lib/op'; + +export function ContactForm() { + const [email, setEmail] = useState(''); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + op.track('form_submitted', { + form_name: 'contact', + form_location: 'homepage', + }); + + // Your form submission logic + }; + + return ( +
+ setEmail(e.target.value)} + placeholder="Enter your email" + /> + +
+ ); +} +``` + +### Use data attributes for declarative tracking + +The Web SDK supports declarative tracking using `data-track` attributes. This is useful for simple click tracking without writing JavaScript. + +```tsx + +``` + +When a user clicks this button, OpenPanel automatically sends a `button_clicked` event with the specified properties. This requires `trackAttributes: true` in your configuration. + +## Identify users [#identify] + +Once a user logs in or provides identifying information, call `identify` to associate their activity with a profile. This enables user-level analytics and cohort analysis. + +```tsx title="src/hooks/useAuth.ts" +import { useEffect } from 'react'; +import { op } from '../lib/op'; + +export function useAuth(user: User | null) { + useEffect(() => { + if (user) { + op.identify({ + profileId: user.id, + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + properties: { + plan: user.plan, + signupDate: user.createdAt, + }, + }); + } + }, [user]); +} +``` + +### Clear user data on logout + +When users log out, clear the stored profile data to ensure subsequent events aren't associated with the previous user. + +```tsx title="src/components/LogoutButton.tsx" +import { op } from '../lib/op'; + +export function LogoutButton({ onLogout }: { onLogout: () => void }) { + const handleLogout = () => { + op.clear(); + onLogout(); + }; + + return ; +} +``` + +### Set global properties + +Properties set with `setGlobalProperties` are included with every event. This is useful for app version tracking, feature flags, or A/B test variants. + +```tsx title="src/App.tsx" +import { useEffect } from 'react'; +import { op } from './lib/op'; + +export default function App() { + useEffect(() => { + op.setGlobalProperties({ + app_version: '1.0.0', + environment: import.meta.env.MODE, + }); + }, []); + + return
{/* Your app */}
; +} +``` + +## Verify your setup [#verify] + +Open your React app in the browser and navigate between a few pages. Interact with elements that trigger custom events. Then open your [OpenPanel dashboard](https://dashboard.openpanel.dev) and check the Real-time view to see events appearing. + +If events aren't appearing, check the browser console for errors. Verify your Client ID is correct and ensure ad blockers aren't blocking requests to the OpenPanel API. The Network tab in your browser's developer tools can help you confirm that requests are being sent. + +## Next steps + +The Web SDK has additional features like property incrementing and event filtering. Read the full [Web SDK documentation](/docs/sdks/web) for the complete API reference. + +For server-side tracking in your React application's API routes, see the [Node.js analytics guide](/guides/nodejs-analytics) which covers the `@openpanel/sdk` package. + + + +Yes. OpenPanel automatically tracks page views when the URL changes, which works with React Router and other routing libraries that use the History API. Set `trackScreenViews: true` in your configuration. + + + +Yes. Import the OpenPanel instance directly and call its methods. The instance is framework-agnostic and works in any JavaScript context. + + + +No. OpenPanel uses cookieless tracking by default. This means you don't need cookie consent banners for basic analytics under most privacy regulations, including GDPR and PECR. + + + +Yes. OpenPanel is designed for GDPR compliance with cookieless tracking, data minimization, and full support for data subject rights. With self-hosting, you also eliminate international data transfer concerns entirely. + + diff --git a/apps/public/content/guides/react-native-analytics.mdx b/apps/public/content/guides/react-native-analytics.mdx new file mode 100644 index 000000000..1a364e54a --- /dev/null +++ b/apps/public/content/guides/react-native-analytics.mdx @@ -0,0 +1,216 @@ +--- +title: "How to add analytics to React Native" +description: "Add privacy-first analytics to your React Native app with OpenPanel. Track screen views, custom events, and user behavior across iOS and Android." +difficulty: intermediate +timeToComplete: 10 +date: 2025-12-14 +cover: /content/cover-default.jpg +team: OpenPanel Team +steps: + - name: "Install the SDK" + anchor: "install" + - name: "Initialize OpenPanel" + anchor: "setup" + - name: "Track screen views" + anchor: "screenviews" + - name: "Track custom events" + anchor: "events" + - name: "Identify users" + anchor: "identify" + - name: "Verify your setup" + anchor: "verify" +--- + +# How to add analytics to React Native + +Adding analytics to your React Native app helps you understand how users interact with your product across both iOS and Android. This guide walks you through setting up OpenPanel to track screen views, custom events, and user behavior in about ten minutes. + +OpenPanel works well with React Native because it handles the complexities of native environments for you. The SDK automatically captures app version, build number, and install referrer on Android. It also queues events when the device is offline and sends them when connectivity returns. + +## Prerequisites + +- A React Native project (Expo or bare React Native) +- An OpenPanel account ([sign up free](https://dashboard.openpanel.dev/onboarding)) +- Your Client ID and Client Secret from the OpenPanel dashboard + +## Install the SDK [#install] + +Start by adding the OpenPanel React Native package and its Expo dependencies. The SDK relies on `expo-application` for version information and `expo-constants` for user-agent data. + +```bash +npm install @openpanel/react-native +npx expo install expo-application expo-constants +``` + +You can also use pnpm or yarn if that's your preference. The Expo packages work in both Expo and bare React Native projects. + +## Initialize OpenPanel [#setup] + +Create an OpenPanel instance that you'll use throughout your app. React Native requires a `clientSecret` for authentication since native apps can't use CORS headers like web browsers do. + +```typescript +import { OpenPanel } from '@openpanel/react-native'; + +export const op = new OpenPanel({ + clientId: 'your-client-id', + clientSecret: 'your-client-secret', +}); +``` + +Put this in a shared file like `lib/op.ts` so you can import it from anywhere in your app. The SDK automatically sets up listeners for app state changes and configures default properties like version and build number. + +## Track screen views [#screenviews] + +Screen view tracking requires hooking into your navigation library. The approach differs slightly depending on whether you use Expo Router or React Navigation. + +If you're using Expo Router, add the tracking call to your root layout component. The `usePathname` hook gives you the current route, and `useSegments` provides the route segments which can be useful for grouping dynamic routes together. + +```typescript +import { usePathname, useSegments } from 'expo-router'; +import { useEffect } from 'react'; +import { op } from '@/lib/op'; + +export default function RootLayout() { + const pathname = usePathname(); + const segments = useSegments(); + + useEffect(() => { + op.screenView(pathname, { + segments: segments.join('/'), + }); + }, [pathname, segments]); + + return ( + // Your layout JSX + ); +} +``` + +For React Navigation, track screen changes using the navigation container's state change callbacks. Create a navigation ref and pass handlers to `onReady` and `onStateChange`. + +```tsx +import { createNavigationContainerRef, NavigationContainer } from '@react-navigation/native'; +import { op } from '@/lib/op'; + +const navigationRef = createNavigationContainerRef(); + +export function App() { + const handleNavigationStateChange = () => { + const current = navigationRef.getCurrentRoute(); + if (current) { + op.screenView(current.name, { + params: current.params, + }); + } + }; + + return ( + + {/* Your navigators */} + + ); +} +``` + +The `onReady` callback fires on initial load, and `onStateChange` fires on subsequent navigations. This ensures you capture every screen view including the first one. + +## Track custom events [#events] + +Beyond screen views, you'll want to track specific interactions that matter to your business. Call `op.track` with an event name and optional properties wherever you need to record user actions. + +```tsx +import { op } from '@/lib/op'; + +function SignupButton() { + const handlePress = () => { + op.track('button_clicked', { + button_name: 'signup', + screen: 'home', + }); + // Continue with signup logic + }; + + return + ); +} +``` + +### Create a tracking hook + +For cleaner code, create a custom hook that handles the dynamic import. + +```tsx title="app/hooks/useOpenPanel.ts" +import { useCallback } from 'react'; +import type { OpenPanel } from '@openpanel/web'; + +type TrackFn = OpenPanel['track']; +type IdentifyFn = OpenPanel['identify']; + +export function useTrack() { + return useCallback((name, properties) => { + import('../lib/op.client').then(({ op }) => { + op.track(name, properties); + }); + }, []); +} + +export function useIdentify() { + return useCallback((payload) => { + import('../lib/op.client').then(({ op }) => { + op.identify(payload); + }); + }, []); +} +``` + +Now your components become cleaner. + +```tsx title="app/components/SignupButton.tsx" +import { useTrack } from '../hooks/useOpenPanel'; + +export function SignupButton() { + const track = useTrack(); + + const handleClick = () => { + track('button_clicked', { + button_name: 'signup', + button_location: 'hero', + }); + }; + + return ( + + ); +} +``` + +### Track form submissions + +Remix encourages using form actions for data mutations. You can track form submissions in your action handlers or on the client. + +```tsx title="app/routes/contact.tsx" +import { Form } from '@remix-run/react'; +import { useTrack } from '../hooks/useOpenPanel'; + +export default function Contact() { + const track = useTrack(); + + const handleSubmit = () => { + track('form_submitted', { + form_name: 'contact', + form_location: 'contact-page', + }); + }; + + return ( +
+ + +
+ ); +} +``` + +### Use data attributes for declarative tracking + +The Web SDK supports declarative tracking using `data-track` attributes. This is useful for simple click tracking without writing JavaScript. + +```tsx + +``` + +When a user clicks this button, OpenPanel automatically sends a `button_clicked` event with the specified properties. This requires `trackAttributes: true` in your configuration. + +## Identify users [#identify] + +Once a user logs in, call `identify` to associate their activity with a profile. In Remix, you typically have user data available from a loader. + +```tsx title="app/routes/dashboard.tsx" +import { useEffect } from 'react'; +import { useLoaderData } from '@remix-run/react'; +import type { LoaderFunctionArgs } from '@remix-run/node'; +import { json } from '@remix-run/node'; +import { getUser } from '../lib/auth.server'; + +export async function loader({ request }: LoaderFunctionArgs) { + const user = await getUser(request); + return json({ user }); +} + +export default function Dashboard() { + const { user } = useLoaderData(); + + useEffect(() => { + if (user) { + import('../lib/op.client').then(({ op }) => { + op.identify({ + profileId: user.id, + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + properties: { + plan: user.plan, + }, + }); + }); + } + }, [user]); + + return
Welcome, {user?.firstName}!
; +} +``` + +### Clear user data on logout + +When users log out, clear the stored profile data to ensure subsequent events aren't associated with the previous user. + +```tsx title="app/components/LogoutButton.tsx" +import { Form } from '@remix-run/react'; + +export function LogoutButton() { + const handleClick = () => { + import('../lib/op.client').then(({ op }) => { + op.clear(); + }); + }; + + return ( +
+ +
+ ); +} +``` + +## Server-side tracking [#server-side] + +For tracking events in loaders, actions, or API routes, use the `@openpanel/sdk` package instead of the web SDK. Server-side tracking requires a client secret. + +```ts title="app/lib/op.server.ts" +import { OpenPanel } from '@openpanel/sdk'; + +export const op = new OpenPanel({ + clientId: process.env.OPENPANEL_CLIENT_ID!, + clientSecret: process.env.OPENPANEL_CLIENT_SECRET!, +}); +``` + +```ts title="app/routes/api.webhook.ts" +import type { ActionFunctionArgs } from '@remix-run/node'; +import { json } from '@remix-run/node'; +import { op } from '../lib/op.server'; + +export async function action({ request }: ActionFunctionArgs) { + const payload = await request.json(); + + op.track('webhook_received', { + source: payload.source, + event_type: payload.type, + }); + + return json({ success: true }); +} +``` + +## Verify your setup [#verify] + +Open your Remix app in the browser and navigate between a few pages. Interact with elements that trigger custom events. Then open your [OpenPanel dashboard](https://dashboard.openpanel.dev) and check the Real-time view to see events appearing. + +If events aren't appearing, check the browser console for errors. Verify your Client ID is correct and ensure ad blockers aren't blocking requests to the OpenPanel API. The Network tab in your browser's developer tools can help you confirm that requests are being sent. + +## Next steps + +The Web SDK has additional features like property incrementing and event filtering. Read the full [Web SDK documentation](/docs/sdks/web) for the complete API reference. + +For comprehensive server-side tracking, see the [Node.js analytics guide](/guides/nodejs-analytics) which covers the `@openpanel/sdk` package in detail. + + + +Yes. OpenPanel's client-side SDK tracks events in the browser after hydration. For server-side events in loaders and actions, use the `@openpanel/sdk` package with your client secret. + + + +Server-side tracking requires authentication since we can't use CORS headers. The client secret ensures your events are properly authenticated and prevents unauthorized tracking. + + + +No. OpenPanel uses cookieless tracking by default. This means you don't need cookie consent banners for basic analytics under most privacy regulations, including GDPR and PECR. + + + +Yes. OpenPanel is designed for GDPR compliance with cookieless tracking, data minimization, and full support for data subject rights. With self-hosting, you also eliminate international data transfer concerns entirely. + + diff --git a/apps/public/content/guides/swift-analytics.mdx b/apps/public/content/guides/swift-analytics.mdx new file mode 100644 index 000000000..9e1fc26d9 --- /dev/null +++ b/apps/public/content/guides/swift-analytics.mdx @@ -0,0 +1,263 @@ +--- +title: "How to Add Analytics to Swift Apps" +description: "Add privacy-first analytics to your iOS, macOS, tvOS, and watchOS apps with OpenPanel's Swift SDK." +difficulty: beginner +timeToComplete: 10 +date: 2025-12-15 +cover: /content/cover-default.jpg +team: OpenPanel Team +steps: + - name: "Add Swift package" + anchor: "install" + - name: "Initialize OpenPanel" + anchor: "setup" + - name: "Track events" + anchor: "events" + - name: "Identify users" + anchor: "identify" + - name: "Track screen views" + anchor: "screenviews" + - name: "Verify your setup" + anchor: "verify" +--- + + +## Introduction + +Understanding how users interact with your native Apple apps requires solid analytics. OpenPanel's Swift SDK gives you event tracking, user identification, and screen view analytics across iOS, macOS, tvOS, and watchOS platforms. + +Since native apps can't rely on CORS headers for authentication like web apps do, the Swift SDK uses a client secret for secure server-side authentication. This makes it suitable for production apps where you need reliable, privacy-respecting analytics. OpenPanel is an open-source alternative to Mixpanel and Amplitude that you can self-host for complete data ownership. + +## Prerequisites + +- Xcode project set up (iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+) +- OpenPanel account ([sign up free](https://dashboard.openpanel.dev/onboarding)) +- Your Client ID and Client Secret from the OpenPanel dashboard + +> **Important:** Native app tracking requires a `clientSecret` for authentication. + +## Step 1: Add Swift package + +The OpenPanel Swift SDK is distributed through Swift Package Manager. Open your project in Xcode, then go to File and select Add Packages. Enter the repository URL and click Add Package. + +``` +https://github.com/Openpanel-dev/swift-sdk +``` + +If you're working with a Package.swift file directly, add OpenPanel as a dependency instead. + +```swift +dependencies: [ + .package(url: "https://github.com/Openpanel-dev/swift-sdk") +] +``` + +## Step 2: Initialize OpenPanel + +Before tracking any events, you need to initialize the SDK when your app launches. This should happen early in your app's lifecycle so the SDK is available throughout your application. + +For UIKit apps, add the initialization to your AppDelegate's `application(_:didFinishLaunchingWithOptions:)` method. + +```swift title="AppDelegate.swift" +import UIKit +import OpenPanel + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + OpenPanel.initialize(options: .init( + clientId: "YOUR_CLIENT_ID", + clientSecret: "YOUR_CLIENT_SECRET" + )) + + return true + } +} +``` + +For SwiftUI apps, initialize the SDK in your App struct's initializer. + +```swift title="MyApp.swift" +import SwiftUI +import OpenPanel + +@main +struct MyApp: App { + init() { + OpenPanel.initialize(options: .init( + clientId: "YOUR_CLIENT_ID", + clientSecret: "YOUR_CLIENT_SECRET" + )) + } + + var body: some Scene { + WindowGroup { + ContentView() + } + } +} +``` + +### Automatic lifecycle tracking + +You can enable automatic lifecycle tracking by setting `automaticTracking: true`. This will track `app_opened` and `app_closed` events for you. + +```swift +OpenPanel.initialize(options: .init( + clientId: "YOUR_CLIENT_ID", + clientSecret: "YOUR_CLIENT_SECRET", + automaticTracking: true +)) +``` + +## Step 3: Track events + +Once initialized, you can track events anywhere in your app by calling `OpenPanel.track`. Each event has a name and optional properties that provide additional context. + +```swift title="SignupButton.swift" +import SwiftUI +import OpenPanel + +struct SignupButton: View { + var body: some View { + Button("Sign Up") { + OpenPanel.track( + name: "button_clicked", + properties: [ + "button_name": "signup", + "button_location": "hero" + ] + ) + } + } +} +``` + +Properties can be any key-value pairs relevant to the event. Common patterns include tracking form submissions, purchases, and feature usage. Keep event names consistent across your app by using snake_case and being descriptive about what action occurred. + +### Set global properties + +If you have properties that should be sent with every event, set them once using `setGlobalProperties`. This is useful for app version, build number, or device information. + +```swift +OpenPanel.setGlobalProperties([ + "app_version": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "", + "platform": UIDevice.current.systemName +]) +``` + +## Step 4: Identify users + +When a user signs in, call `identify` to associate their events with their profile. This enables you to track user journeys across sessions and understand individual user behavior. + +```swift title="AuthService.swift" +OpenPanel.identify(payload: IdentifyPayload( + profileId: user.id, + firstName: user.firstName, + lastName: user.lastName, + email: user.email, + properties: [ + "plan": user.plan, + "signup_date": user.createdAt.ISO8601Format() + ] +)) +``` + +The `profileId` should be a unique identifier for the user, typically from your authentication system. Additional properties like `firstName`, `lastName`, and `email` help you recognize users in your dashboard. + +### Clear user data on logout + +When a user logs out, call `clear` to reset the local state. This ensures subsequent events aren't incorrectly attributed to the previous user. + +```swift +func logout() { + OpenPanel.clear() +} +``` + +### Increment user properties + +You can also increment numeric properties on user profiles. This is useful for tracking counts like logins or purchases without needing to fetch and update the current value. + +```swift +OpenPanel.increment(payload: IncrementPayload( + profileId: user.id, + property: "login_count" +)) +``` + +## Step 5: Track screen views + +Tracking screen views helps you understand how users navigate through your app. In SwiftUI, use the `onAppear` modifier to track when a view becomes visible. + +```swift title="HomeView.swift" +struct HomeView: View { + var body: some View { + VStack { + Text("Home") + } + .onAppear { + OpenPanel.track( + name: "screen_view", + properties: ["screen_name": "HomeScreen"] + ) + } + } +} +``` + +In UIKit, override `viewDidAppear` in your view controllers. + +```swift title="HomeViewController.swift" +class HomeViewController: UIViewController { + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + OpenPanel.track( + name: "screen_view", + properties: [ + "screen_name": "HomeScreen", + "screen_class": String(describing: type(of: self)) + ] + ) + } +} +``` + +The OpenPanel SDK is designed to be thread-safe. You can call its methods from any thread without additional synchronization. + +## Verify your setup + +Run your app in the simulator or on a physical device and perform some actions. Navigate between screens and tap a few buttons to generate events. Then open your [OpenPanel dashboard](https://dashboard.openpanel.dev) and check the real-time view. + +**Not seeing events?** + +- Check the Xcode console for any error messages +- Verify that your Client ID and Client Secret are correct +- Confirm that you included the `clientSecret` parameter (required for native apps) +- Test with a stable network connection first + +## Next steps + +The [Swift SDK reference](/docs/sdks/swift) covers additional configuration options like event filtering and disabling tracking. If you're building a cross-platform mobile app, the [React Native analytics guide](/guides/react-native-analytics) shows how to set up OpenPanel in that environment. + +## FAQ + +### Why do I need a clientSecret for native apps? + +Native apps can't use CORS headers for authentication like web applications can. The clientSecret provides secure server-side authentication for your events, ensuring they're properly validated before being recorded. + +### Does OpenPanel work with both SwiftUI and UIKit? + +Yes. OpenPanel works with both UIKit and SwiftUI. Use the `.onAppear` modifier in SwiftUI views or lifecycle methods like `viewDidAppear` in UIKit view controllers to track screen views and other events. + +### Can I track events when the user is offline? + +OpenPanel queues events locally and sends them when network connectivity is restored. Events won't be lost if the user temporarily goes offline. + +### Is OpenPanel GDPR compliant? + +Yes. OpenPanel is designed for GDPR compliance with data minimization and support for data subject rights. With self-hosting, you also eliminate international data transfer concerns entirely. See the [cookieless analytics article](/articles/cookieless-analytics) for more details. diff --git a/apps/public/content/guides/track-custom-events.mdx b/apps/public/content/guides/track-custom-events.mdx new file mode 100644 index 000000000..c132a0aec --- /dev/null +++ b/apps/public/content/guides/track-custom-events.mdx @@ -0,0 +1,226 @@ +--- +title: "How to track custom events with OpenPanel" +description: "Learn how to track custom events like button clicks, form submissions, and user interactions in OpenPanel." +difficulty: beginner +timeToComplete: 5 +date: 2025-12-15 +cover: /content/cover-default.jpg +team: OpenPanel Team +steps: + - name: "Understand event structure" + anchor: "event-structure" + - name: "Track simple events" + anchor: "track-simple-events" + - name: "Track events with properties" + anchor: "track-events-with-properties" + - name: "Use data attributes" + anchor: "data-attributes" + - name: "Verify your setup" + anchor: "verify" +--- + +# How to track custom events with OpenPanel + +Custom events are the foundation of product analytics. They let you track specific user actions like button clicks, form submissions, video plays, and purchases. This guide walks you through tracking custom events in OpenPanel across different platforms. + +OpenPanel provides a consistent API for event tracking across all SDKs. Once you understand the pattern, you can apply it to any integration. + +## Prerequisites + +- An OpenPanel account ([sign up free](https://dashboard.openpanel.dev/onboarding)) +- OpenPanel SDK installed in your project +- Your Client ID from the dashboard + +## Understand event structure [#event-structure] + +Every event in OpenPanel consists of two parts: a name and optional properties. The name describes what happened, while properties add context about how, where, and when it happened. + +A well-named event reads like a sentence in past tense. Instead of naming an event "click" or "button", use descriptive names like `signup_button_clicked` or `purchase_completed`. This makes your analytics dashboard immediately understandable. + +For property names, use snake_case and keep them consistent across your application. If you track `button_name` on one event, use the same property name on similar events rather than switching to `buttonName` or `name`. + +## Track simple events [#track-simple-events] + +The simplest event is just a name with no properties. This works well for actions where the context is obvious, like clicking a specific button that only appears in one place. + +If you're using the Web SDK with npm, import your OpenPanel instance and call the track method. + +```typescript +import { op } from './op'; + +op.track('signup_button_clicked'); +``` + +For the script tag integration, use the global `window.op` function with 'track' as the first argument. + +```html + +``` + +If you're tracking events server-side with the JavaScript SDK, the pattern is identical. + +```typescript +import { op } from './op'; + +op.track('order_placed'); +``` + +The track method queues events and sends them asynchronously, so it won't block your application logic. + +## Track events with properties [#track-events-with-properties] + +Properties transform raw events into actionable data. Instead of knowing that someone clicked a button, you can know which button, where it was located, and what the user was doing at the time. + +Pass properties as the second argument to the track method. Include any context that will help you segment and analyze the data later. + +```typescript +import { op } from './op'; + +op.track('button_clicked', { + button_name: 'signup', + button_location: 'hero_section', + page: 'homepage', +}); +``` + +With the script tag, pass the properties object as the third argument. + +```html + +``` + +Think carefully about which properties to include. Track data that helps you answer questions about user behavior, like understanding which signup button performs best or which page drives the most conversions. + +Here's an example of tracking a purchase with meaningful properties. + +```typescript +op.track('purchase_completed', { + product_id: 'prod_123', + product_name: 'Premium Plan', + amount: 99.99, + currency: 'USD', + payment_method: 'credit_card', + previous_plan: 'free', +}); +``` + +Never include sensitive data in event properties. Passwords, credit card numbers, and personally identifiable information should never appear in your analytics. + +## Use data attributes [#data-attributes] + +For HTML elements, OpenPanel supports declarative tracking with `data-track` attributes. This approach works well when you want to add tracking without writing JavaScript handlers. + +Add `data-track` with the event name, then use additional `data-*` attributes for properties. + +```html + +``` + +The SDK automatically converts kebab-case attribute names to snake_case properties. So `data-button-name` becomes `button_name` in your event data. + +For complex or nested properties, use `data-track-properties` with a JSON string. + +```html + +``` + +Data attributes require the `trackAttributes: true` option in your SDK initialization. If you're not seeing events from data attributes, check that this option is enabled. + +## Common event patterns + +Form submissions benefit from tracking both the submission and key context about what was submitted. + +```typescript +function handleSubmit(event) { + event.preventDefault(); + + op.track('form_submitted', { + form_name: 'contact', + form_location: 'footer', + fields_completed: 3, + }); + + // Submit the form +} +``` + +For video interactions, track play, pause, and completion events with timing information. + +```typescript +function handleVideoPlay(video) { + op.track('video_played', { + video_id: video.id, + video_title: video.title, + video_duration: video.duration, + }); +} + +function handleVideoComplete(video) { + op.track('video_completed', { + video_id: video.id, + watch_time: video.currentTime, + }); +} +``` + +Search events should include the query and results count to help you understand what users are looking for. + +```typescript +function handleSearch(query, results) { + op.track('search_performed', { + query: query, + query_length: query.length, + results_count: results.length, + has_results: results.length > 0, + }); +} +``` + +## Verify your setup [#verify] + +After implementing event tracking, open your OpenPanel dashboard and navigate to the Real-time view. Trigger the events in your application and confirm they appear within a few seconds. + +If events aren't showing up, check that your Client ID is correct and that the SDK initialized without errors. Browser developer tools can help you verify that tracking requests are being sent. Look for network requests to `openpanel.dev` or your self-hosted endpoint. + +## Next steps + +Now that you're tracking custom events, you can identify users to connect events to specific people. For analyzing your event data, the [funnel analysis guide](/articles/how-to-create-a-funnel) shows you how to measure conversion rates across multi-step flows. You can also explore the [SDK documentation](/docs/sdks/web) for advanced features like global properties and event filtering. + +For framework-specific examples and setup instructions, check out: +- [Next.js analytics guide](/guides/nextjs-analytics) for Next.js applications +- [React analytics guide](/guides/react-analytics) for React applications +- [Node.js analytics guide](/guides/nodejs-analytics) for server-side tracking +- [Python analytics guide](/guides/python-analytics) for Python applications + + + +OpenPanel doesn't limit the number of events you can track. Focus on tracking meaningful events that help you understand user behavior and make product decisions. + + + +No. Track events that help you make decisions. Tracking every click creates noise and makes it harder to find insights. Focus on actions that indicate user intent or progress toward a goal. + + + +Yes. The [Node.js SDK](/docs/sdks/javascript) supports server-side tracking with the same API. Server-side tracking is useful for events that happen outside the browser, like webhook callbacks or background jobs. + + + +No. OpenPanel uses cookieless tracking by default, so you don't need cookie consent banners for basic analytics under most privacy regulations. + + diff --git a/apps/public/content/guides/vue-analytics.mdx b/apps/public/content/guides/vue-analytics.mdx new file mode 100644 index 000000000..96181ebdd --- /dev/null +++ b/apps/public/content/guides/vue-analytics.mdx @@ -0,0 +1,348 @@ +--- +title: "How to add analytics to Vue" +description: "Add privacy-first analytics to your Vue application with OpenPanel's Web SDK. Track page views, custom events, and user behavior." +difficulty: beginner +timeToComplete: 8 +date: 2025-12-14 +cover: /content/cover-default.jpg +team: OpenPanel Team +steps: + - name: "Install the SDK" + anchor: "install" + - name: "Create an OpenPanel instance" + anchor: "setup" + - name: "Track page views" + anchor: "pageviews" + - name: "Track custom events" + anchor: "events" + - name: "Identify users" + anchor: "identify" + - name: "Verify your setup" + anchor: "verify" +--- + +Adding analytics to your Vue application helps you understand how users interact with your app. OpenPanel's Web SDK integrates smoothly with Vue 3 and Vue Router, providing automatic page view tracking, custom events, and user identification without requiring Vue-specific bindings. + +OpenPanel is an open-source alternative to Mixpanel and Google Analytics. It delivers powerful insights while respecting user privacy through cookieless tracking by default. + +## Prerequisites + +- A Vue 3 project (Vite or Vue CLI) +- An OpenPanel account ([sign up free](https://dashboard.openpanel.dev/onboarding)) +- Your Client ID from the OpenPanel dashboard + +## Install the SDK [#install] + +The OpenPanel Web SDK is a lightweight package that works in any JavaScript environment, including Vue. Install it using npm, and pnpm or yarn work the same way. + +```bash +npm install @openpanel/web +``` + +## Create an OpenPanel instance [#setup] + +Create a dedicated file for your OpenPanel instance. This centralizes your analytics configuration and makes the instance easy to import throughout your application. + +```ts title="src/lib/op.ts" +import { OpenPanel } from '@openpanel/web'; + +export const op = new OpenPanel({ + clientId: 'YOUR_CLIENT_ID', + trackScreenViews: true, + trackOutgoingLinks: true, + trackAttributes: true, +}); +``` + +The `trackScreenViews` option automatically tracks page views when the URL changes. This works with Vue Router's client-side navigation. The `trackAttributes` option enables declarative tracking using `data-track` attributes on HTML elements. + +### Using environment variables + +For production applications, store your Client ID in environment variables. + +```ts title="src/lib/op.ts" +import { OpenPanel } from '@openpanel/web'; + +export const op = new OpenPanel({ + clientId: import.meta.env.VITE_OPENPANEL_CLIENT_ID, + trackScreenViews: true, + trackOutgoingLinks: true, + trackAttributes: true, +}); +``` + +```bash title=".env" +VITE_OPENPANEL_CLIENT_ID=your-client-id +``` + +### Initialize on app load + +Import the OpenPanel instance in your app's entry point to ensure it initializes when your application loads. This is all you need to start tracking page views automatically. + +```ts title="src/main.ts" +import { createApp } from 'vue'; +import App from './App.vue'; +import router from './router'; +import './lib/op'; // Initialize OpenPanel + +const app = createApp(App); +app.use(router); +app.mount('#app'); +``` + +### Make OpenPanel available globally (optional) + +If you prefer accessing OpenPanel through the Vue instance rather than importing it in each component, you can add it as a global property. + +```ts title="src/main.ts" +import { createApp } from 'vue'; +import App from './App.vue'; +import router from './router'; +import { op } from './lib/op'; + +const app = createApp(App); +app.config.globalProperties.$op = op; +app.use(router); +app.mount('#app'); +``` + +This makes `this.$op` available in Options API components and allows you to inject it in Composition API components. + +## Track page views [#pageviews] + +With `trackScreenViews: true`, OpenPanel automatically tracks page views when the browser's URL changes. This works out of the box with Vue Router. + +If you need to track page views manually or want to include additional route metadata, you can use a Vue Router navigation guard. + +```ts title="src/router/index.ts" +import { createRouter, createWebHistory } from 'vue-router'; +import { op } from '../lib/op'; +import routes from './routes'; + +const router = createRouter({ + history: createWebHistory(), + routes, +}); + +router.afterEach((to) => { + op.track('screen_view', { + path: to.path, + name: String(to.name), + }); +}); + +export default router; +``` + +If you use this approach, set `trackScreenViews: false` in your OpenPanel configuration to avoid duplicate tracking. + +## Track custom events [#events] + +Import the OpenPanel instance in your components to track events. The `track` method accepts an event name and an optional properties object. + +### Using Composition API + +```vue title="src/components/SignupButton.vue" + + + +``` + +### Using Options API + +```vue title="src/components/SignupButton.vue" + + + +``` + +### Track form submissions + +Form tracking helps you understand conversion rates and identify where users drop off. + +```vue title="src/components/ContactForm.vue" + + + +``` + +### Use data attributes for declarative tracking + +The Web SDK supports declarative tracking using `data-track` attributes. This is useful for simple click tracking without writing JavaScript. + +```vue + +``` + +When a user clicks this button, OpenPanel automatically sends a `button_clicked` event with the specified properties. This requires `trackAttributes: true` in your configuration. + +## Identify users [#identify] + +Once a user logs in or provides identifying information, call `identify` to associate their activity with a profile. This enables user-level analytics and cohort analysis. + +```vue title="src/components/UserProfile.vue" + + + +``` + +### Clear user data on logout + +When users log out, clear the stored profile data to ensure subsequent events aren't associated with the previous user. + +```vue title="src/components/LogoutButton.vue" + + + +``` + +### Set global properties + +Properties set with `setGlobalProperties` are included with every event. This is useful for app version tracking, feature flags, or A/B test variants. + +```vue title="src/App.vue" + +``` + +## Verify your setup [#verify] + +Open your Vue app in the browser and navigate between a few pages. Interact with elements that trigger custom events. Then open your [OpenPanel dashboard](https://dashboard.openpanel.dev) and check the Real-time view to see events appearing. + +If events aren't appearing, check the browser console for errors. Verify your Client ID is correct and ensure ad blockers aren't blocking requests to the OpenPanel API. The Network tab in your browser's developer tools can help you confirm that requests are being sent. + +## Next steps + +The Web SDK has additional features like property incrementing and event filtering. Read the full [Web SDK documentation](/docs/sdks/web) for the complete API reference. + +For Nuxt.js applications, the setup is similar. Initialize OpenPanel in a Nuxt plugin and the automatic page view tracking will work with Nuxt's router. + + + +Yes. OpenPanel automatically tracks page views when the URL changes, which works with Vue Router. Set `trackScreenViews: true` in your configuration. You can also use router navigation guards for manual tracking. + + + +Yes. The OpenPanel Web SDK is framework-agnostic and works with Vue 2. Import the instance directly in your components and call its methods. + + + +No. OpenPanel uses cookieless tracking by default. This means you don't need cookie consent banners for basic analytics under most privacy regulations, including GDPR and PECR. + + + +Yes. OpenPanel is designed for GDPR compliance with cookieless tracking, data minimization, and full support for data subject rights. With self-hosting, you also eliminate international data transfer concerns entirely. + + diff --git a/apps/public/content/guides/website-analytics-setup.mdx b/apps/public/content/guides/website-analytics-setup.mdx new file mode 100644 index 000000000..446aa4d2f --- /dev/null +++ b/apps/public/content/guides/website-analytics-setup.mdx @@ -0,0 +1,178 @@ +--- +title: "How to add analytics to any website" +description: "Add privacy-first analytics to any website in minutes using a simple script tag. Works with WordPress, Webflow, Squarespace, and plain HTML." +difficulty: beginner +timeToComplete: 5 +date: 2025-12-14 +cover: /content/cover-default.jpg +team: OpenPanel Team +steps: + - name: "Get your Client ID" + anchor: "get-id" + - name: "Add the script tag" + anchor: "script" + - name: "Track custom events" + anchor: "events" + - name: "Verify your setup" + anchor: "verify" +--- + +# How to add analytics to any website + +Adding analytics to your website does not require developer expertise. OpenPanel's script tag works with any website, whether you're using WordPress, Webflow, Squarespace, or plain HTML. In about five minutes, you'll have privacy-first analytics running on your site. + +OpenPanel is an open-source analytics platform that works without cookies by default. This means you can track meaningful user behavior while respecting privacy and avoiding cookie consent banners in most cases. + +## Prerequisites + +- A website on any platform +- An OpenPanel account ([sign up free](https://dashboard.openpanel.dev/onboarding)) +- Access to your website's HTML header or footer + +## Get your Client ID [#get-id] + +Start by creating an OpenPanel account at [dashboard.openpanel.dev](https://dashboard.openpanel.dev/onboarding). Once logged in, create a new project and copy your Client ID from the project settings. + +Your Client ID will look something like `cl_xxxxxxxxxxxxxxxx`. Keep this handy, as you'll need it in the next step. + +## Add the script tag [#script] + +The OpenPanel script needs to be added to every page of your website. The best approach depends on your platform, but the core snippet remains the same. + +Here's the script tag you'll be adding. Replace `YOUR_CLIENT_ID` with the Client ID you copied earlier. + +```html + + +``` + +The `trackScreenViews` option enables automatic page view tracking, so you don't need to manually track each page. The `trackOutgoingLinks` option captures clicks on external links, which is useful for understanding how users leave your site. + +### Platform-specific instructions + +For WordPress sites, the easiest approach is to use a plugin like "Insert Headers and Footers" or "Code Snippets". Install the plugin, then paste the script tag into the header section. If you prefer working with code, you can add the script directly to your theme's `header.php` file before the closing `` tag. + +WordPress users who want more control can add the script via `functions.php` instead. + +```php +function add_openpanel_script() { + ?> + + + Advanced > Code Injection. Add the script to the Header section and save your changes. + +If you're using Google Tag Manager, create a new Custom HTML tag and paste the script tag code. Set the trigger to All Pages and publish your container. + +## Track custom events [#events] + +Page views are tracked automatically, but you'll likely want to track specific user actions like button clicks, form submissions, or video plays. OpenPanel provides two ways to do this. + +The simplest approach uses `data-track` attributes directly in your HTML. When a user clicks an element with this attribute, OpenPanel automatically sends an event. + +```html + +``` + +Any `data-` attributes on the element (except `data-track` itself) are included as event properties. This is useful when you want to track additional context without writing JavaScript. + +For more complex tracking, you can call the `window.op` function directly. This gives you full control over when and what to track. + +```html + +``` + +Form submissions are a common tracking use case. You can track them inline using the `onsubmit` attribute. + +```html +
+ + +
+``` + +### Identifying users + +When users log in or provide their email, you can associate their activity with a profile. This is done using the `identify` method. + +```html + +``` + +The `profileId` is required and should be a unique identifier from your system. Once identified, all subsequent events are associated with this user, enabling cross-session analysis. + +## Verify your setup [#verify] + +Open your website in a browser and navigate through a few pages. Click some buttons or links that you've set up tracking for. Then head to your [OpenPanel dashboard](https://dashboard.openpanel.dev) and check the Real-time view. + +Events should appear within a few seconds. If you're not seeing any data, open your browser's developer console (F12) and look for JavaScript errors. The most common issues are an incorrect Client ID or ad blockers preventing the script from loading. + +Ad blockers can interfere with analytics scripts. If this is a concern for your audience, you can proxy the OpenPanel script through your own domain. The [ad blocker documentation](/docs/adblockers) explains how to set this up. + +## Next steps + +The script tag covers most tracking needs for traditional websites. For more advanced configuration options, check out the [Script Tag SDK reference](/docs/sdks/script). If you want to understand user journeys better, the article on [how to create a funnel](/articles/how-to-create-a-funnel) walks through setting up conversion funnels. + +For sites with backend logic or server-side rendering, you might want to combine client-side tracking with server-side events. The [Node.js guide](/guides/nodejs-analytics) and [Python guide](/guides/python-analytics) cover those use cases. + +If you're using a specific framework, check out our framework-specific guides for more advanced setups: +- [Next.js analytics guide](/guides/nextjs-analytics) for Next.js applications +- [React analytics guide](/guides/react-analytics) for React applications +- [Astro analytics guide](/guides/astro-analytics) for Astro sites +- [Vue analytics guide](/guides/vue-analytics) for Vue.js applications + + + +No. OpenPanel uses cookieless tracking by default. This means you typically don't need cookie consent banners for basic analytics under most privacy regulations, including GDPR and PECR. + + + +Some ad blockers may block requests to openpanel.dev. You can mitigate this by proxying the script through your own domain or by self-hosting OpenPanel. The documentation covers both approaches. + + + +Yes. Add the OpenPanel script as a Custom HTML tag in Google Tag Manager with an All Pages trigger. This lets you manage OpenPanel alongside your other tracking scripts. + + + +Yes. OpenPanel is designed for GDPR compliance with cookieless tracking, data minimization, and support for data subject rights. Self-hosting eliminates international data transfer concerns entirely. Read more about [cookieless analytics](/articles/cookieless-analytics) for details. + + diff --git a/apps/public/source.config.ts b/apps/public/source.config.ts index 40bebe746..552c64bd3 100644 --- a/apps/public/source.config.ts +++ b/apps/public/source.config.ts @@ -36,6 +36,23 @@ const zPage = z.object({ description: z.string(), }); +const zGuide = z.object({ + title: z.string().min(1), + description: z.string(), + difficulty: z.enum(['beginner', 'intermediate', 'advanced']), + timeToComplete: z.number(), // minutes + date: z.date(), + updated: z.date().optional(), + cover: z.string().default('/content/cover-default.jpg'), + team: z.string().optional(), + steps: z.array( + z.object({ + name: z.string(), + anchor: z.string(), + }), + ), +}); + export const articleCollection = defineCollections({ type: 'doc', dir: './content/articles', @@ -60,6 +77,18 @@ export const pageMeta = defineCollections({ schema: zPage, }); +export const guideCollection = defineCollections({ + type: 'doc', + dir: './content/guides', + schema: zGuide, +}); + +export const guideMeta = defineCollections({ + type: 'meta', + dir: './content/guides', + schema: zGuide, +}); + export default defineConfig({ mdxOptions: { // MDX options diff --git a/apps/public/src/app/(content)/guides/[guideSlug]/page.tsx b/apps/public/src/app/(content)/guides/[guideSlug]/page.tsx new file mode 100644 index 000000000..61ff43677 --- /dev/null +++ b/apps/public/src/app/(content)/guides/[guideSlug]/page.tsx @@ -0,0 +1,212 @@ +import { CtaBanner } from '@/app/(home)/_sections/cta-banner'; +import { HeroContainer } from '@/app/(home)/_sections/hero'; +import { Testimonials } from '@/app/(home)/_sections/testimonials'; +import { FeatureCardContainer } from '@/components/feature-card'; +import { GetStartedButton } from '@/components/get-started-button'; +import { GuideCard } from '@/components/guide-card'; +import { Logo } from '@/components/logo'; +import { SectionHeader } from '@/components/section'; +import { Toc } from '@/components/toc'; +import { url, getAuthor } from '@/lib/layout.shared'; +import { getOgImageUrl, getPageMetadata } from '@/lib/metadata'; +import { guideSource } from '@/lib/source'; +import { getMDXComponents } from '@/mdx-components'; +import { ArrowLeftIcon, ClockIcon } from 'lucide-react'; +import type { Metadata } from 'next'; +import Image from 'next/image'; +import Link from 'next/link'; +import { notFound } from 'next/navigation'; +import Script from 'next/script'; + +const difficultyColors = { + beginner: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200', + intermediate: + 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200', + advanced: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200', +}; + +const difficultyLabels = { + beginner: 'Beginner', + intermediate: 'Intermediate', + advanced: 'Advanced', +}; + +export async function generateStaticParams() { + const guides = await guideSource.getPages(); + return guides.map((guide) => { + // Extract slug from URL (e.g., '/guides/my-guide' -> 'my-guide') + const slug = guide.url.replace(/^\/guides\//, '').replace(/\/$/, ''); + return { guideSlug: slug }; + }); +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ guideSlug: string }>; +}): Promise { + const { guideSlug } = await params; + const guide = await guideSource.getPage([guideSlug]); + + if (!guide) { + return { + title: 'Guide Not Found', + }; + } + + return getPageMetadata({ + title: guide.data.title, + description: guide.data.description, + url: url(guide.url), + image: getOgImageUrl(guide.url), + }); +} + +export default async function Page({ + params, +}: { + params: Promise<{ guideSlug: string }>; +}) { + const { guideSlug } = await params; + const guide = await guideSource.getPage([guideSlug]); + const Body = guide?.data.body; + const author = getAuthor(guide?.data.team); + const goBackUrl = '/guides'; + + const relatedGuides = (await guideSource.getPages()) + .filter( + (item) => + item.data.difficulty === guide?.data.difficulty && + item.url !== guide?.url, + ) + .sort((a, b) => b.data.date.getTime() - a.data.date.getTime()) + .slice(0, 3); + + if (!Body) { + return notFound(); + } + + const slug = guide.url.replace(/^\/guides\//, '').replace(/\/$/, ''); + + // Create the HowTo JSON-LD schema + const jsonLd = { + '@context': 'https://schema.org', + '@type': 'HowTo', + name: guide?.data.title, + description: guide?.data.description, + totalTime: `PT${guide?.data.timeToComplete}M`, + step: guide?.data.steps.map((step, i) => ({ + '@type': 'HowToStep', + position: i + 1, + name: step.name, + url: url(`/guides/${slug}#${step.anchor}`), + })), + }; + + return ( +
+ +
+ + + Back to all guides + + +
+
+ {author.image ? ( + {author.name} + ) : ( + + )} +
+
+

{author.name}

+
+

+ {guide?.data.date.toLocaleDateString()} +

+ {guide?.data.updated && ( +

+ Updated on {guide?.data.updated.toLocaleDateString()} +

+ )} +
+
+
+ + {difficultyLabels[guide?.data.difficulty || 'beginner']} + +
+ + {guide?.data.timeToComplete} min +
+
+
+
+
+