From 738c8e24d610dec9319152f588c6df62cf6c2a28 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sat, 7 Feb 2026 00:05:52 +0100 Subject: [PATCH 1/4] feat: add more integration pages --- app/integrations/angular/page.tsx | 63 +++++ app/integrations/astro/page.tsx | 60 +++++ app/integrations/gatsby/page.tsx | 65 +++++ app/integrations/ghost/page.tsx | 55 ++++ app/integrations/hugo/page.tsx | 65 +++++ app/integrations/nextjs/page.tsx | 116 +++----- app/integrations/nuxt/page.tsx | 71 +++++ app/integrations/page.tsx | 166 +++++------- app/integrations/react/page.tsx | 103 +++---- app/integrations/remix/page.tsx | 71 +++++ app/integrations/shopify/page.tsx | 53 ++++ app/integrations/squarespace/page.tsx | 52 ++++ app/integrations/svelte/page.tsx | 70 +++++ app/integrations/vue/page.tsx | 114 ++------ app/integrations/webflow/page.tsx | 55 ++++ app/integrations/wix/page.tsx | 54 ++++ app/integrations/wordpress/page.tsx | 101 +++---- components/CodeBlock.tsx | 30 +++ components/IntegrationGuide.tsx | 69 +++++ lib/integrations.tsx | 268 +++++++++++++++++++ public/Icon Padding left & right 192x192.png | Bin 0 -> 7962 bytes public/Icon Padding left & right 512x512.png | Bin 0 -> 22728 bytes public/sw 2.js | 1 + 23 files changed, 1298 insertions(+), 404 deletions(-) create mode 100644 app/integrations/angular/page.tsx create mode 100644 app/integrations/astro/page.tsx create mode 100644 app/integrations/gatsby/page.tsx create mode 100644 app/integrations/ghost/page.tsx create mode 100644 app/integrations/hugo/page.tsx create mode 100644 app/integrations/nuxt/page.tsx create mode 100644 app/integrations/remix/page.tsx create mode 100644 app/integrations/shopify/page.tsx create mode 100644 app/integrations/squarespace/page.tsx create mode 100644 app/integrations/svelte/page.tsx create mode 100644 app/integrations/webflow/page.tsx create mode 100644 app/integrations/wix/page.tsx create mode 100644 components/CodeBlock.tsx create mode 100644 components/IntegrationGuide.tsx create mode 100644 lib/integrations.tsx create mode 100644 public/Icon Padding left & right 192x192.png create mode 100644 public/Icon Padding left & right 512x512.png create mode 100644 public/sw 2.js diff --git a/app/integrations/angular/page.tsx b/app/integrations/angular/page.tsx new file mode 100644 index 0000000..e8833cb --- /dev/null +++ b/app/integrations/angular/page.tsx @@ -0,0 +1,63 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function AngularIntegrationPage() { + const integration = getIntegration('angular') + if (!integration) return notFound() + + return ( + +

+ Add Pulse to your Angular application by placing the script in your index.html or by using the Angular CLI's built-in scripts array. +

+ +
+ +

Method 1: index.html (Recommended)

+

+ Add the Pulse script tag directly to the <head> section of your src/index.html. +

+ + +{` + + + + My Angular App + + + + + + + + + +`} + + +

Method 2: angular.json Scripts Array

+

+ Alternatively, reference an external script in your angular.json build configuration. However, for analytics scripts that need defer and data-* attributes, Method 1 is simpler and recommended. +

+ +

Configuration Options

+ +
+ ) +} diff --git a/app/integrations/astro/page.tsx b/app/integrations/astro/page.tsx new file mode 100644 index 0000000..004c687 --- /dev/null +++ b/app/integrations/astro/page.tsx @@ -0,0 +1,60 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function AstroIntegrationPage() { + const integration = getIntegration('astro') + if (!integration) return notFound() + + return ( + +

+ Astro makes it easy to add third-party scripts. Drop the Pulse snippet into your base layout and you're done. +

+ +
+ +

Base Layout (Recommended)

+

+ Add the script to the <head> of your base layout file so it loads on every page. +

+ + +{`--- +// Base layout used by all pages +--- + + + + + + + + + My Astro Site + + + + +`} + + +

Using Astro's Script Integration

+

+ You can also configure the script in your astro.config.mjs using the injectScript API of an Astro integration, but the layout approach above is simpler for most projects. +

+ +

Astro + View Transitions

+

+ If you use Astro's View Transitions, the Pulse script persists across navigations automatically since it is loaded in the <head> with defer. +

+
+ ) +} diff --git a/app/integrations/gatsby/page.tsx b/app/integrations/gatsby/page.tsx new file mode 100644 index 0000000..5191b88 --- /dev/null +++ b/app/integrations/gatsby/page.tsx @@ -0,0 +1,65 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function GatsbyIntegrationPage() { + const integration = getIntegration('gatsby') + if (!integration) return notFound() + + return ( + +

+ Add Pulse to your Gatsby site using the gatsby-ssr API or the Gatsby Head API. +

+ +
+ +

Method 1: gatsby-ssr.js (Recommended)

+

+ Use the onRenderBody API to inject the script into every page's <head>. +

+ + +{`import React from "react" + +export const onRenderBody = ({ setHeadComponents }) => { + setHeadComponents([ + `} + + +
    +
  1. Click Save.
  2. +
+ +

Theme-Level Integration (Alternative)

+

+ If you prefer, you can also add the script directly to your Ghost theme's default.hbs file, just before the closing </head> tag. This approach requires re-uploading the theme whenever you make changes. +

+ +

Important Notes

+ +
+ ) +} diff --git a/app/integrations/hugo/page.tsx b/app/integrations/hugo/page.tsx new file mode 100644 index 0000000..2aa0b6f --- /dev/null +++ b/app/integrations/hugo/page.tsx @@ -0,0 +1,65 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function HugoIntegrationPage() { + const integration = getIntegration('hugo') + if (!integration) return notFound() + + return ( + +

+ Add Pulse to your Hugo site by placing the script in a partial or directly in your base template. +

+ +
+ +

Method 1: Partial (Recommended)

+

+ Create an analytics partial and include it in your base template's <head>. +

+ + +{`{{ if not .Site.IsServer }} + +{{ end }}`} + + +

+ Then include the partial in your baseof.html: +

+ + +{` + + + + + {{ .Title }} + + {{ partial "analytics.html" . }} + + + {{ block "main" . }}{{ end }} + +`} + + +

Method 2: Direct Insertion

+

+ If you prefer, add the script tag directly to the <head> of your baseof.html without creating a partial. +

+ +

+ The if not .Site.IsServer guard ensures the script is excluded during local development with hugo server. +

+
+ ) +} diff --git a/app/integrations/nextjs/page.tsx b/app/integrations/nextjs/page.tsx index 270e73a..5704ab3 100644 --- a/app/integrations/nextjs/page.tsx +++ b/app/integrations/nextjs/page.tsx @@ -1,59 +1,28 @@ 'use client' -import Link from 'next/link' -import { ArrowLeftIcon } from '@ciphera-net/ui' +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' export default function NextJsIntegrationPage() { + const integration = getIntegration('nextjs') + if (!integration) return notFound() + return ( -
- {/* * --- ATMOSPHERE (Background) --- */} -
-
-
-
-
+ +

+ The best way to add Pulse to your Next.js application is using the built-in next/script component. +

-
- - - Back to Integrations - +
-
-
- - - -
-

- Next.js Integration -

-
+

Using App Router (Recommended)

+

+ Add the script to your root layout file (usually app/layout.tsx or app/layout.js). +

-
-

- The best way to add Pulse to your Next.js application is using the built-in next/script component. -

- -
- -

Using App Router (Recommended)

-

- Add the script to your root layout file (usually app/layout.tsx or app/layout.js). -

- -
-
- app/layout.tsx -
-
-
+      
 {`import Script from 'next/script'
 
 export default function RootLayout({
@@ -75,21 +44,14 @@ export default function RootLayout({
     
   )
 }`}
-              
-
-
+ -

Using Pages Router

-

- If you are using the older Pages Router, add the script to your custom _app.tsx or _document.tsx. -

+

Using Pages Router

+

+ If you are using the older Pages Router, add the script to your custom _app.tsx or _document.tsx. +

-
-
- pages/_app.tsx -
-
-
+      
 {`import Script from 'next/script'
 import type { AppProps } from 'next/app'
 
@@ -106,24 +68,20 @@ export default function App({ Component, pageProps }: AppProps) {
     
   )
 }`}
-              
-
-
+ -

Configuration Options

-
    -
  • - data-domain: The domain name you added to your Pulse dashboard (e.g., example.com). -
  • -
  • - src: The URL of our tracking script: https://pulse.ciphera.net/script.js -
  • -
  • - strategy: We recommend afterInteractive to ensure it loads quickly without blocking hydration. -
  • -
-
-
-
+

Configuration Options

+
    +
  • + data-domain: The domain name you added to your Pulse dashboard (e.g., example.com). +
  • +
  • + src: The URL of our tracking script: https://pulse.ciphera.net/script.js +
  • +
  • + strategy: We recommend afterInteractive to ensure it loads quickly without blocking hydration. +
  • +
+ ) } diff --git a/app/integrations/nuxt/page.tsx b/app/integrations/nuxt/page.tsx new file mode 100644 index 0000000..2b5241c --- /dev/null +++ b/app/integrations/nuxt/page.tsx @@ -0,0 +1,71 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function NuxtIntegrationPage() { + const integration = getIntegration('nuxt') + if (!integration) return notFound() + + return ( + +

+ Configure Pulse in your Nuxt application by adding the script to your nuxt.config file. +

+ +
+ +

Nuxt 3 (Recommended)

+

+ Add the script to the app.head section of your nuxt.config.ts. +

+ + +{`export default defineNuxtConfig({ + app: { + head: { + script: [ + { + src: 'https://pulse.ciphera.net/script.js', + defer: true, + 'data-domain': 'your-site.com' + } + ] + } + } +})`} + + +

Nuxt 2

+

+ For Nuxt 2 projects, add the script to the head object in nuxt.config.js. +

+ + +{`export default { + head: { + script: [ + { + src: 'https://pulse.ciphera.net/script.js', + defer: true, + 'data-domain': 'your-site.com' + } + ] + } +}`} + + +

Configuration Options

+
    +
  • + data-domain: The domain name you added to your Pulse dashboard (e.g., example.com). +
  • +
  • + defer: Ensures the script loads without blocking rendering. +
  • +
+
+ ) +} diff --git a/app/integrations/page.tsx b/app/integrations/page.tsx index dabb37e..7d9df36 100644 --- a/app/integrations/page.tsx +++ b/app/integrations/page.tsx @@ -3,60 +3,18 @@ import Link from 'next/link' import { motion } from 'framer-motion' import { ArrowRightIcon } from '@ciphera-net/ui' - -const integrations = [ - { - id: 'nextjs', - name: 'Next.js', - description: 'Add privacy-friendly analytics to your Next.js application using next/script.', - icon: ( - - - - ), - }, - { - id: 'react', - name: 'React', - description: 'Integrate Pulse with any React SPA (Create React App, Vite, etc).', - icon: ( - - - - - ), - }, - { - id: 'vue', - name: 'Vue.js', - description: 'Simple setup for Vue 2 and Vue 3 applications.', - icon: ( - - - - - ), - }, - { - id: 'wordpress', - name: 'WordPress', - description: 'Add the tracking script to your WordPress header or use a plugin.', - icon: ( - - - - ), - }, -] +import { getGroupedIntegrations } from '@/lib/integrations' export default function IntegrationsPage() { + const groups = getGroupedIntegrations() + return (
{/* * --- ATMOSPHERE (Background) --- */}
-
@@ -77,61 +35,75 @@ export default function IntegrationsPage() {

-
- {integrations.map((integration, i) => ( - ( +
+ - -
-
- {integration.icon} -
- -
- -

- {integration.name} -

-

- {integration.description} -

- - View Guide - - - - ))} - - {/* * Request Integration Card */} - + +
+ {group.items.map((integration, i) => ( + + +
+
+ {integration.icon} +
+ +
+ +

+ {integration.name} +

+

+ {integration.description} +

+ + View Guide + + +
+ ))} +
+
+ ))} + + {/* * Request Integration Card */} + +

+ Missing something? +

+

+ Let us know which integration you'd like to see next. +

+ -

- Missing something? -

-

- Let us know which integration you'd like to see next. -

-
- Request Integration - -
-
+ Request Integration + +
) diff --git a/app/integrations/react/page.tsx b/app/integrations/react/page.tsx index d8d07cb..bee5df9 100644 --- a/app/integrations/react/page.tsx +++ b/app/integrations/react/page.tsx @@ -1,94 +1,55 @@ 'use client' -import Link from 'next/link' -import { ArrowLeftIcon } from '@ciphera-net/ui' +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' export default function ReactIntegrationPage() { + const integration = getIntegration('react') + if (!integration) return notFound() + return ( -
- {/* * --- ATMOSPHERE (Background) --- */} -
-
-
-
-
+ +

+ For standard React SPAs (Create React App, Vite, etc.), you can simply add the script tag to your index.html. +

-
- - - Back to Integrations - +
-
-
- - - - -
-

- React Integration -

-
+

Method 1: index.html (Recommended)

+

+ The simplest way is to add the script tag directly to the <head> of your index.html file. +

-
-

- For standard React SPAs (Create React App, Vite, etc.), you can simply add the script tag to your index.html. -

- -
- -

Method 1: index.html (Recommended)

-

- The simplest way is to add the script tag directly to the <head> of your index.html file. -

- -
-
- public/index.html -
-
-
+      
 {`
 
   
     
     
-    
+
     
-    
-    
+
     My React App
   
   
     
`} -
-
-
+ -

Method 2: Programmatic Injection

-

- If you need to load the script dynamically (e.g., only in production), you can use a useEffect hook in your main App component. -

+

Method 2: Programmatic Injection

+

+ If you need to load the script dynamically (e.g., only in production), you can use a useEffect hook in your main App component. +

-
-
- src/App.tsx -
-
-
+      
 {`import { useEffect } from 'react'
 
 function App() {
@@ -109,11 +70,7 @@ function App() {
     
) }`} - -
-
-
-
-
+ + ) } diff --git a/app/integrations/remix/page.tsx b/app/integrations/remix/page.tsx new file mode 100644 index 0000000..a7d7bcc --- /dev/null +++ b/app/integrations/remix/page.tsx @@ -0,0 +1,71 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function RemixIntegrationPage() { + const integration = getIntegration('remix') + if (!integration) return notFound() + + return ( + +

+ Add Pulse to your Remix application by placing the script tag in your root route's <head>. +

+ +
+ +

Root Route (Recommended)

+

+ In Remix, the app/root.tsx file controls the HTML shell. Add the Pulse script inside the <head> section. +

+ + +{`import { + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from "@remix-run/react" + +export default function App() { + return ( + + + + + + + + {/* Pulse Analytics */} + `} + + +

Method 2: Custom Pixels (Shopify Plus)

+

+ If you are on Shopify Plus, you can also use Customer Events > Custom Pixels to add the script. Go to Settings > Customer events and create a new custom pixel. +

+ +

Important Notes

+
    +
  • + data-domain: Use your custom domain (e.g., shop.example.com) if you have one, or your .myshopify.com domain. +
  • +
  • + Pulse does not use cookies and is fully GDPR-compliant — no cookie banner changes needed. +
  • +
+
+ ) +} diff --git a/app/integrations/squarespace/page.tsx b/app/integrations/squarespace/page.tsx new file mode 100644 index 0000000..a428a61 --- /dev/null +++ b/app/integrations/squarespace/page.tsx @@ -0,0 +1,52 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function SquarespaceIntegrationPage() { + const integration = getIntegration('squarespace') + if (!integration) return notFound() + + return ( + +

+ Add Pulse to your Squarespace site using the built-in Code Injection feature — no plugins needed. +

+ +
+ +

Code Injection (Recommended)

+
    +
  1. In your Squarespace dashboard, go to Settings > Developer Tools > Code Injection.
  2. +
  3. In the Header field, paste the following snippet:
  4. +
+ + +{``} + + +
    +
  1. Click Save.
  2. +
+ +

Important Notes

+
    +
  • + Code Injection is available on Squarespace Business and Commerce plans. +
  • +
  • + data-domain: Use your custom domain (e.g., example.com) rather than the .squarespace.com subdomain. +
  • +
  • + Pulse is cookie-free, so you do not need to update your Squarespace cookie banner settings. +
  • +
+
+ ) +} diff --git a/app/integrations/svelte/page.tsx b/app/integrations/svelte/page.tsx new file mode 100644 index 0000000..b123581 --- /dev/null +++ b/app/integrations/svelte/page.tsx @@ -0,0 +1,70 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function SvelteIntegrationPage() { + const integration = getIntegration('svelte') + if (!integration) return notFound() + + return ( + +

+ Integrating Pulse with Svelte or SvelteKit takes less than a minute. Just add the script tag to your HTML entry point. +

+ +
+ +

Svelte (Vite)

+

+ For a standard Svelte project scaffolded with Vite, add the script to the <head> of your index.html. +

+ + +{` + + + + + + + + + My Svelte App + + +
+ + +`} +
+ +

SvelteKit

+

+ In SvelteKit, add the script to your root layout's <svelte:head> block so it loads on every page. +

+ + +{` + + + +`} + + +

+ Alternatively, you can add the script to src/app.html directly in the <head> section. +

+
+ ) +} diff --git a/app/integrations/vue/page.tsx b/app/integrations/vue/page.tsx index f5bd66e..c9278df 100644 --- a/app/integrations/vue/page.tsx +++ b/app/integrations/vue/page.tsx @@ -1,73 +1,41 @@ 'use client' -import Link from 'next/link' -import { ArrowLeftIcon } from '@ciphera-net/ui' +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' export default function VueIntegrationPage() { + const integration = getIntegration('vue') + if (!integration) return notFound() + return ( -
- {/* * --- ATMOSPHERE (Background) --- */} -
-
-
-
-
+ +

+ Integrating Pulse with Vue.js is straightforward. Add the script to your index.html file. +

-
- - - Back to Integrations - +
-
-
- - - - -
-

- Vue.js Integration -

-
+

index.html (Vue CLI & Vite)

+

+ Add the script tag to the <head> section of your index.html file. This works for both Vue 2 and Vue 3 projects created with Vue CLI or Vite. +

-
-

- Integrating Pulse with Vue.js is straightforward. You can add the script to your index.html file. -

- -
- -

Method 1: index.html (Recommended)

-

- Add the script tag to the <head> section of your index.html file. This works for both Vue 2 and Vue 3 projects created with Vue CLI or Vite. -

- -
-
- index.html -
-
-
+      
 {`
 
   
     
     
-    
+
     
-    
-    
+
     My Vue App
   
   
@@ -75,39 +43,11 @@ export default function VueIntegrationPage() {
     
   
 `}
-              
-
-
+ -

Method 2: Nuxt.js

-

- For Nuxt.js applications, you should add the script to your nuxt.config.js or nuxt.config.ts file. -

- -
-
- nuxt.config.ts -
-
-
-{`export default defineNuxtConfig({
-  app: {
-    head: {
-      script: [
-        {
-          src: 'https://pulse.ciphera.net/script.js',
-          defer: true,
-          'data-domain': 'your-site.com'
-        }
-      ]
-    }
-  }
-})`}
-              
-
-
-
-
-
+

+ Looking for Nuxt.js? Check the dedicated Nuxt integration guide. +

+ ) } diff --git a/app/integrations/webflow/page.tsx b/app/integrations/webflow/page.tsx new file mode 100644 index 0000000..a6a3e93 --- /dev/null +++ b/app/integrations/webflow/page.tsx @@ -0,0 +1,55 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function WebflowIntegrationPage() { + const integration = getIntegration('webflow') + if (!integration) return notFound() + + return ( + +

+ Add Pulse to your Webflow site by pasting a single snippet into your project's custom code settings. +

+ +
+ +

Project-Level Custom Code (Recommended)

+
    +
  1. Open your Webflow project and go to Project Settings.
  2. +
  3. Navigate to the Custom Code tab.
  4. +
  5. In the Head Code section, paste the following snippet:
  6. +
+ + +{``} + + +
    +
  1. Click Save Changes and publish your site.
  2. +
+ +

Page-Level Custom Code

+

+ If you only want to track specific pages, you can add the script to individual page settings instead of the project-level settings. Go to the page's settings panel and paste the snippet in the Head Code section. +

+ +

Important Notes

+
    +
  • + Custom code requires a Webflow paid site plan (Basic or higher). +
  • +
  • + The script will not appear in the Webflow Designer preview — publish the site and view the live version to verify. +
  • +
+
+ ) +} diff --git a/app/integrations/wix/page.tsx b/app/integrations/wix/page.tsx new file mode 100644 index 0000000..db49e28 --- /dev/null +++ b/app/integrations/wix/page.tsx @@ -0,0 +1,54 @@ +'use client' + +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' + +export default function WixIntegrationPage() { + const integration = getIntegration('wix') + if (!integration) return notFound() + + return ( + +

+ Add Pulse to your Wix site using the Custom Code feature in your site settings. +

+ +
+ +

Custom Code (Recommended)

+
    +
  1. In your Wix dashboard, go to Settings > Custom Code (under “Advanced”).
  2. +
  3. Click + Add Custom Code.
  4. +
  5. Paste the following snippet:
  6. +
+ + +{``} + + +
    +
  1. Set the code to load in the Head of All pages.
  2. +
  3. Click Apply.
  4. +
+ +

Important Notes

+
    +
  • + Custom Code requires a Wix Premium plan with a connected domain. +
  • +
  • + data-domain: Use your connected custom domain, not the .wixsite.com subdomain. +
  • +
  • + Pulse is cookie-free and GDPR-compliant — no consent banner changes are needed. +
  • +
+
+ ) +} diff --git a/app/integrations/wordpress/page.tsx b/app/integrations/wordpress/page.tsx index 873a2cf..c8d9a6b 100644 --- a/app/integrations/wordpress/page.tsx +++ b/app/integrations/wordpress/page.tsx @@ -1,81 +1,46 @@ 'use client' -import Link from 'next/link' -import { ArrowLeftIcon } from '@ciphera-net/ui' +import { IntegrationGuide } from '@/components/IntegrationGuide' +import { CodeBlock } from '@/components/CodeBlock' +import { getIntegration } from '@/lib/integrations' +import { notFound } from 'next/navigation' export default function WordPressIntegrationPage() { + const integration = getIntegration('wordpress') + if (!integration) return notFound() + return ( -
- {/* * --- ATMOSPHERE (Background) --- */} -
-
-
-
-
+ +

+ You can add Pulse to your WordPress site without installing any heavy plugins, or by using a simple code snippet plugin. +

-
- - - Back to Integrations - +
-
-
- - - -
-

- WordPress Integration -

-
+

Method 1: Using a Plugin (Easiest)

+
    +
  1. Install a plugin like “Insert Headers and Footers” (WPCode).
  2. +
  3. Go to the plugin settings and find the “Scripts in Header” section.
  4. +
  5. Paste the following code snippet:
  6. +
-
-

- You can add Pulse to your WordPress site without installing any heavy plugins, or by using a simple code snippet plugin. -

- -
- -

Method 1: Using a Plugin (Easiest)

-
    -
  1. Install a plugin like "Insert Headers and Footers" (WPCode).
  2. -
  3. Go to the plugin settings and find the "Scripts in Header" section.
  4. -
  5. Paste the following code snippet:
  6. -
- -
-
- Header Script -
-
-
-{``}
-              
-
-
+ -

Method 2: Edit Theme Files (Advanced)

-

- If you are comfortable editing your theme files, you can add the script directly to your header.php file. -

-
    -
  1. Go to Appearance > Theme File Editor.
  2. -
  3. Select header.php from the right sidebar.
  4. -
  5. Paste the script tag just before the closing </head> tag.
  6. -
-
-
-
+

Method 2: Edit Theme Files (Advanced)

+

+ If you are comfortable editing your theme files, you can add the script directly to your header.php file. +

+
    +
  1. Go to Appearance > Theme File Editor.
  2. +
  3. Select header.php from the right sidebar.
  4. +
  5. Paste the script tag just before the closing </head> tag.
  6. +
+ ) } diff --git a/components/CodeBlock.tsx b/components/CodeBlock.tsx new file mode 100644 index 0000000..48f4c81 --- /dev/null +++ b/components/CodeBlock.tsx @@ -0,0 +1,30 @@ +'use client' + +/** + * @file Reusable code block component for integration guide pages. + * + * Renders a VS-Code-style code block with a filename tab header. + */ + +interface CodeBlockProps { + /** Filename displayed in the tab header */ + filename: string + /** The code string to render inside the block */ + children: string +} + +/** + * Renders a dark-themed code snippet with a filename tab. + */ +export function CodeBlock({ filename, children }: CodeBlockProps) { + return ( +
+
+ {filename} +
+
+
{children}
+
+
+ ) +} diff --git a/components/IntegrationGuide.tsx b/components/IntegrationGuide.tsx new file mode 100644 index 0000000..236fb8e --- /dev/null +++ b/components/IntegrationGuide.tsx @@ -0,0 +1,69 @@ +'use client' + +/** + * @file Shared layout component for individual integration guide pages. + * + * Provides the background atmosphere, back-link, header (logo + title), + * and prose-styled content area used by every integration sub-page. + */ + +import Link from 'next/link' +import { ArrowLeftIcon } from '@ciphera-net/ui' +import { type ReactNode } from 'react' +import { type Integration } from '@/lib/integrations' + +interface IntegrationGuideProps { + /** Integration metadata (name, icon, etc.) */ + integration: Integration + /** Guide content rendered inside the prose area */ + children: ReactNode +} + +/** + * Renders the full-page layout for a single integration guide. + */ +export function IntegrationGuide({ integration, children }: IntegrationGuideProps) { + // * Scale the icon up for the detail-page header (w-10 h-10) + const headerIcon = ( +
+ {integration.icon} +
+ ) + + return ( +
+ {/* * --- ATMOSPHERE (Background) --- */} +
+
+
+
+
+ +
+ + + Back to Integrations + + +
+
+ {headerIcon} +
+

+ {integration.name} Integration +

+
+ +
+ {children} +
+
+
+ ) +} diff --git a/lib/integrations.tsx b/lib/integrations.tsx new file mode 100644 index 0000000..18aefd4 --- /dev/null +++ b/lib/integrations.tsx @@ -0,0 +1,268 @@ +/** + * @file Integration metadata and official SVG logos. + * + * ! SVG paths sourced from simple-icons (https://simpleicons.org). + * All icons use a 24×24 viewBox. + */ + +import { type ReactNode } from 'react' + +// * ─── Types ────────────────────────────────────────────────────────────────── + +export type IntegrationCategory = 'framework' | 'cms' | 'ssg' | 'platform' + +export interface Integration { + id: string + name: string + description: string + category: IntegrationCategory + /** Brand hex colour (with #) */ + brandColor: string + /** Whether the icon needs `dark:invert` (black-only logos) */ + invertInDark?: boolean + /** Official 24×24 SVG as a React node */ + icon: ReactNode +} + +// * ─── Category labels (for UI grouping) ────────────────────────────────────── + +export const categoryLabels: Record = { + framework: 'Frameworks', + cms: 'CMS & Blogging', + ssg: 'Static-Site Generators', + platform: 'Platforms & Builders', +} + +export const categoryOrder: IntegrationCategory[] = [ + 'framework', + 'platform', + 'ssg', + 'cms', +] + +// * ─── Integration registry ────────────────────────────────────────────────── + +export const integrations: Integration[] = [ + // * ── Frameworks ─────────────────────────────────────────────────────────── + { + id: 'nextjs', + name: 'Next.js', + description: 'Add privacy-friendly analytics to your Next.js application using next/script.', + category: 'framework', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + }, + { + id: 'react', + name: 'React', + description: 'Integrate Pulse with any React SPA (Create React App, Vite, etc).', + category: 'framework', + brandColor: '#61DAFB', + icon: ( + + + + ), + }, + { + id: 'vue', + name: 'Vue.js', + description: 'Simple setup for Vue 2 and Vue 3 applications.', + category: 'framework', + brandColor: '#4FC08D', + icon: ( + + + + ), + }, + { + id: 'angular', + name: 'Angular', + description: 'Add Pulse analytics to your Angular application with a simple script tag.', + category: 'framework', + brandColor: '#0F0F11', + invertInDark: true, + icon: ( + + + + ), + }, + { + id: 'svelte', + name: 'Svelte', + description: 'Integrate Pulse with Svelte or SvelteKit in under a minute.', + category: 'framework', + brandColor: '#FF3E00', + icon: ( + + + + ), + }, + { + id: 'nuxt', + name: 'Nuxt', + description: 'Configure Pulse in your Nuxt application via nuxt.config.', + category: 'framework', + brandColor: '#00DC82', + icon: ( + + + + ), + }, + { + id: 'remix', + name: 'Remix', + description: 'Add Pulse to your Remix app via the root route loader.', + category: 'framework', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + }, + { + id: 'astro', + name: 'Astro', + description: 'Integrate Pulse with your Astro site for lightning-fast analytics.', + category: 'framework', + brandColor: '#BC52EE', + icon: ( + + + + ), + }, + { + id: 'gatsby', + name: 'Gatsby', + description: 'Add Pulse to your Gatsby site via gatsby-ssr or a plugin.', + category: 'ssg', + brandColor: '#663399', + icon: ( + + + + ), + }, + + // * ── Static-Site Generators ──────────────────────────────────────────────── + { + id: 'hugo', + name: 'Hugo', + description: 'Drop the Pulse script into your Hugo partial or base template.', + category: 'ssg', + brandColor: '#FF4088', + icon: ( + + + + ), + }, + + // * ── CMS & Blogging ─────────────────────────────────────────────────────── + { + id: 'wordpress', + name: 'WordPress', + description: 'Add the tracking script to your WordPress header or use a plugin.', + category: 'cms', + brandColor: '#21759B', + icon: ( + + + + ), + }, + { + id: 'ghost', + name: 'Ghost', + description: 'Inject Pulse into your Ghost theme via Code Injection settings.', + category: 'cms', + brandColor: '#15171A', + invertInDark: true, + icon: ( + + + + ), + }, + + // * ── Platforms & Builders ────────────────────────────────────────────────── + { + id: 'shopify', + name: 'Shopify', + description: 'Add privacy-first analytics to your Shopify store via theme editor.', + category: 'platform', + brandColor: '#7AB55C', + icon: ( + + + + ), + }, + { + id: 'webflow', + name: 'Webflow', + description: 'Paste the Pulse snippet into your Webflow project custom code.', + category: 'platform', + brandColor: '#146EF5', + icon: ( + + + + ), + }, + { + id: 'squarespace', + name: 'Squarespace', + description: 'Add Pulse to Squarespace via the Code Injection panel.', + category: 'platform', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + }, + { + id: 'wix', + name: 'Wix', + description: 'Add Pulse to your Wix site via the Custom Code settings.', + category: 'platform', + brandColor: '#0C6EFC', + icon: ( + + + + ), + }, +] + +// * ─── Helpers ──────────────────────────────────────────────────────────────── + +/** Retrieve a single integration by its route slug. */ +export function getIntegration(id: string): Integration | undefined { + return integrations.find((i) => i.id === id) +} + +/** Group integrations by category, preserving category ordering. */ +export function getGroupedIntegrations(): { category: IntegrationCategory; label: string; items: Integration[] }[] { + return categoryOrder + .map((cat) => ({ + category: cat, + label: categoryLabels[cat], + items: integrations.filter((i) => i.category === cat), + })) + .filter((group) => group.items.length > 0) +} diff --git a/public/Icon Padding left & right 192x192.png b/public/Icon Padding left & right 192x192.png new file mode 100644 index 0000000000000000000000000000000000000000..c3b532c41088c454ad16db1f561085474f86dca3 GIT binary patch literal 7962 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 Sz8(Fgt*NG~TB&Rk_WuBsts`~- literal 0 HcmV?d00001 diff --git a/public/Icon Padding left & right 512x512.png b/public/Icon Padding left & right 512x512.png new file mode 100644 index 0000000000000000000000000000000000000000..a9bf4b0c063fd675f5935ebf151227caffcb7bbf GIT binary patch literal 22728 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 ik9^(a=new URL(a+".js",n).href,s[a]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=a,e.onload=s,document.head.appendChild(e)}else e=a,importScripts(a),s()}).then(()=>{let e=s[a];if(!e)throw new Error(`Module ${a} didn’t register its module`);return e}));self.define=(n,c)=>{const i=e||("document"in self?document.currentScript.src:"")||location.href;if(s[i])return;let t={};const r=e=>a(e,i),d={module:{uri:i},exports:t,require:r};s[i]=Promise.all(n.map(e=>d[e]||r(e))).then(e=>(c(...e),t))}}define(["./workbox-f1770938"],function(e){"use strict";importScripts(),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/Icon Padding left & right 192x192.png",revision:"a9bae788a2253361a0e295deadbcc083"},{url:"/Icon Padding left & right 512x512.png",revision:"d2efb02d7b9a0a627387d334201e6ea7"},{url:"/_next/static/chunks/0e5ce63c-0d9b48ee367f70a2.js",revision:"0d9b48ee367f70a2"},{url:"/_next/static/chunks/164f4fb6-67211e558fa5de32.js",revision:"67211e558fa5de32"},{url:"/_next/static/chunks/1795-59339c2cb7f5fe6c.js",revision:"59339c2cb7f5fe6c"},{url:"/_next/static/chunks/1992.d220c685821eae19.js",revision:"d220c685821eae19"},{url:"/_next/static/chunks/201a89a4-3d566664e60259ff.js",revision:"3d566664e60259ff"},{url:"/_next/static/chunks/2170a4aa-a1f2fd30878a3e91.js",revision:"a1f2fd30878a3e91"},{url:"/_next/static/chunks/2f0b94e8-ce53c98b232310fc.js",revision:"ce53c98b232310fc"},{url:"/_next/static/chunks/30a37ab2-30992cf05be9404a.js",revision:"30992cf05be9404a"},{url:"/_next/static/chunks/4573-3b13da9808e514ac.js",revision:"3b13da9808e514ac"},{url:"/_next/static/chunks/4bd1b696-e5d7c65570c947b7.js",revision:"e5d7c65570c947b7"},{url:"/_next/static/chunks/5902.6a7448e0dec4f9e1.js",revision:"6a7448e0dec4f9e1"},{url:"/_next/static/chunks/5909-cf645368838a6b20.js",revision:"cf645368838a6b20"},{url:"/_next/static/chunks/7620-7f79d135fa03ba32.js",revision:"7f79d135fa03ba32"},{url:"/_next/static/chunks/795d4814-4c5954750245c540.js",revision:"4c5954750245c540"},{url:"/_next/static/chunks/8500-98e13bcce54aa7a0.js",revision:"98e13bcce54aa7a0"},{url:"/_next/static/chunks/8928-835918834d3b3798.js",revision:"835918834d3b3798"},{url:"/_next/static/chunks/8e1d74a4-407f656e5bcc2171.js",revision:"407f656e5bcc2171"},{url:"/_next/static/chunks/ad2866b8.6c51983a1eb56136.js",revision:"6c51983a1eb56136"},{url:"/_next/static/chunks/app/_global-error/page-85628b53985e66de.js",revision:"85628b53985e66de"},{url:"/_next/static/chunks/app/_not-found/page-85628b53985e66de.js",revision:"85628b53985e66de"},{url:"/_next/static/chunks/app/about/page-961134dbb15bfcbf.js",revision:"961134dbb15bfcbf"},{url:"/_next/static/chunks/app/api/auth/refresh/route-85628b53985e66de.js",revision:"85628b53985e66de"},{url:"/_next/static/chunks/app/auth/callback/page-e02d5207bc967b15.js",revision:"e02d5207bc967b15"},{url:"/_next/static/chunks/app/faq/page-fac70f1a93ec3606.js",revision:"fac70f1a93ec3606"},{url:"/_next/static/chunks/app/installation/page-f2243076dd49d3d4.js",revision:"f2243076dd49d3d4"},{url:"/_next/static/chunks/app/integrations/nextjs/page-ba7437bf718723f9.js",revision:"ba7437bf718723f9"},{url:"/_next/static/chunks/app/integrations/page-cef8333acda65483.js",revision:"cef8333acda65483"},{url:"/_next/static/chunks/app/integrations/react/page-410fd66b109e333c.js",revision:"410fd66b109e333c"},{url:"/_next/static/chunks/app/integrations/vue/page-d815efd4c9a39306.js",revision:"d815efd4c9a39306"},{url:"/_next/static/chunks/app/integrations/wordpress/page-f03e2378c00b904a.js",revision:"f03e2378c00b904a"},{url:"/_next/static/chunks/app/layout-ce4924adc7c42dcc.js",revision:"ce4924adc7c42dcc"},{url:"/_next/static/chunks/app/login/page-96640fc203cae231.js",revision:"96640fc203cae231"},{url:"/_next/static/chunks/app/not-found-833b37ab1663c8a1.js",revision:"833b37ab1663c8a1"},{url:"/_next/static/chunks/app/onboarding/page-091dd498ed5d5805.js",revision:"091dd498ed5d5805"},{url:"/_next/static/chunks/app/org-settings/page-c3e177ad6171617e.js",revision:"c3e177ad6171617e"},{url:"/_next/static/chunks/app/page-1d0749d506de7405.js",revision:"1d0749d506de7405"},{url:"/_next/static/chunks/app/pricing/page-ff5fab384b0203d3.js",revision:"ff5fab384b0203d3"},{url:"/_next/static/chunks/app/settings/page-890f2c485c894bb2.js",revision:"890f2c485c894bb2"},{url:"/_next/static/chunks/app/share/%5Bid%5D/page-88df4e94e7b55109.js",revision:"88df4e94e7b55109"},{url:"/_next/static/chunks/app/signup/page-b17cb3afb210ddbb.js",revision:"b17cb3afb210ddbb"},{url:"/_next/static/chunks/app/sites/%5Bid%5D/page-616162d6a1d6e47c.js",revision:"616162d6a1d6e47c"},{url:"/_next/static/chunks/app/sites/%5Bid%5D/realtime/page-2a2d04563947fa67.js",revision:"2a2d04563947fa67"},{url:"/_next/static/chunks/app/sites/%5Bid%5D/settings/page-b0fd17efb70e114b.js",revision:"b0fd17efb70e114b"},{url:"/_next/static/chunks/app/sites/new/page-c8dddbd443e2a8ac.js",revision:"c8dddbd443e2a8ac"},{url:"/_next/static/chunks/bc98253f.1c4ca5773e357da2.js",revision:"1c4ca5773e357da2"},{url:"/_next/static/chunks/c9d33fe5-86a53cc7ffd40559.js",revision:"86a53cc7ffd40559"},{url:"/_next/static/chunks/ee560e2c-d269d85d3d10b933.js",revision:"d269d85d3d10b933"},{url:"/_next/static/chunks/f4898fe8-8bc2ae55335cc57d.js",revision:"8bc2ae55335cc57d"},{url:"/_next/static/chunks/framework-81b2e59ffe13bb24.js",revision:"81b2e59ffe13bb24"},{url:"/_next/static/chunks/main-2aa77a4a2c547977.js",revision:"2aa77a4a2c547977"},{url:"/_next/static/chunks/main-app-e646227faa58df82.js",revision:"e646227faa58df82"},{url:"/_next/static/chunks/next/dist/client/components/builtin/app-error-85628b53985e66de.js",revision:"85628b53985e66de"},{url:"/_next/static/chunks/next/dist/client/components/builtin/forbidden-85628b53985e66de.js",revision:"85628b53985e66de"},{url:"/_next/static/chunks/next/dist/client/components/builtin/global-error-68b36e74ca32f8b6.js",revision:"68b36e74ca32f8b6"},{url:"/_next/static/chunks/next/dist/client/components/builtin/unauthorized-85628b53985e66de.js",revision:"85628b53985e66de"},{url:"/_next/static/chunks/polyfills-42372ed130431b0a.js",revision:"846118c33b2c0e922d7b3a7676f81f6f"},{url:"/_next/static/chunks/webpack-4c5e072e59638097.js",revision:"4c5e072e59638097"},{url:"/_next/static/css/9f42e6b7c3cab03c.css",revision:"9f42e6b7c3cab03c"},{url:"/_next/static/media/636a5ac981f94f8b-s.p.woff2",revision:"52d04440a9faae0db9adc6cdc844099b"},{url:"/_next/static/media/6fe53d21e6e7ebd8-s.woff2",revision:"2591db816b61d44b6e87ba79d13622b2"},{url:"/_next/static/media/8ebc6e9dde468c4a-s.woff2",revision:"196acbb650c75807ea2f0ef36edbd186"},{url:"/_next/static/media/9e7b0a821b9dfcb4-s.woff2",revision:"5ffe46eeb00dd9fa8a70cb10ccc3817e"},{url:"/_next/static/yV0hJZnpczr3hupoivroz/_buildManifest.js",revision:"f603baae985f0e99def0a2c51922a169"},{url:"/_next/static/yV0hJZnpczr3hupoivroz/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/ciphera_icon_no_margins.png",revision:"eccd951fa34bd0901dcc37461b605e5b"},{url:"/favicon.ico",revision:"d256b0f8c516bbd3e93edf343876b462"},{url:"/icon-192x192.png",revision:"a9bae788a2253361a0e295deadbcc083"},{url:"/icon-512x512.png",revision:"d2efb02d7b9a0a627387d334201e6ea7"},{url:"/manifest.json",revision:"9c84335d179dcab05bf43ff586f2420d"},{url:"/pulse_icon_no_margins.png",revision:"a7cd4faa1c8e42ea5654451a31565642"},{url:"/pulse_logo_no_margins.png",revision:"6d6553cc7ea96597ffdcb69db9e0d11f"},{url:"/script.js",revision:"c579f58e59235c8575fb8262a26f411f"}],{ignoreURLParametersMatching:[/^utm_/,/^fbclid$/]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({response:e})=>e&&"opaqueredirect"===e.type?new Response(e.body,{status:200,statusText:"OK",headers:e.headers}):e}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3})]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800})]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:2592e3})]}),"GET"),e.registerRoute(/\/_next\/static.+\.js$/i,new e.CacheFirst({cacheName:"next-static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:mp4|webm)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:48,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({sameOrigin:e,url:{pathname:s}})=>!(!e||s.startsWith("/api/auth/callback")||!s.startsWith("/api/")),new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({request:e,url:{pathname:s},sameOrigin:a})=>"1"===e.headers.get("RSC")&&"1"===e.headers.get("Next-Router-Prefetch")&&a&&!s.startsWith("/api/"),new e.NetworkFirst({cacheName:"pages-rsc-prefetch",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({request:e,url:{pathname:s},sameOrigin:a})=>"1"===e.headers.get("RSC")&&a&&!s.startsWith("/api/"),new e.NetworkFirst({cacheName:"pages-rsc",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({url:{pathname:e},sameOrigin:s})=>s&&!e.startsWith("/api/"),new e.NetworkFirst({cacheName:"pages",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400})]}),"GET"),e.registerRoute(({sameOrigin:e})=>!e,new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600})]}),"GET")}); From a074ba7dd9789d2befd1b84bc26fff816ac9f0db Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sat, 7 Feb 2026 00:56:20 +0100 Subject: [PATCH 2/4] feat: enhance integrations overview with search functionality and related integrations display --- app/integrations/[slug]/page.tsx | 111 ++ app/integrations/angular/page.tsx | 63 - app/integrations/astro/page.tsx | 60 - app/integrations/gatsby/page.tsx | 65 - app/integrations/ghost/page.tsx | 55 - app/integrations/hugo/page.tsx | 65 - app/integrations/nextjs/page.tsx | 87 - app/integrations/nuxt/page.tsx | 71 - app/integrations/page.tsx | 263 ++- app/integrations/react/page.tsx | 76 - app/integrations/remix/page.tsx | 71 - app/integrations/shopify/page.tsx | 53 - app/integrations/squarespace/page.tsx | 52 - app/integrations/svelte/page.tsx | 70 - app/integrations/vue/page.tsx | 53 - app/integrations/webflow/page.tsx | 55 - app/integrations/wix/page.tsx | 54 - app/integrations/wordpress/page.tsx | 46 - components/CodeBlock.tsx | 2 - components/IntegrationGuide.tsx | 48 +- lib/integration-guides.tsx | 2632 +++++++++++++++++++++++++ lib/integrations.tsx | 676 ++++++- 22 files changed, 3638 insertions(+), 1090 deletions(-) create mode 100644 app/integrations/[slug]/page.tsx delete mode 100644 app/integrations/angular/page.tsx delete mode 100644 app/integrations/astro/page.tsx delete mode 100644 app/integrations/gatsby/page.tsx delete mode 100644 app/integrations/ghost/page.tsx delete mode 100644 app/integrations/hugo/page.tsx delete mode 100644 app/integrations/nextjs/page.tsx delete mode 100644 app/integrations/nuxt/page.tsx delete mode 100644 app/integrations/react/page.tsx delete mode 100644 app/integrations/remix/page.tsx delete mode 100644 app/integrations/shopify/page.tsx delete mode 100644 app/integrations/squarespace/page.tsx delete mode 100644 app/integrations/svelte/page.tsx delete mode 100644 app/integrations/vue/page.tsx delete mode 100644 app/integrations/webflow/page.tsx delete mode 100644 app/integrations/wix/page.tsx delete mode 100644 app/integrations/wordpress/page.tsx create mode 100644 lib/integration-guides.tsx diff --git a/app/integrations/[slug]/page.tsx b/app/integrations/[slug]/page.tsx new file mode 100644 index 0000000..f9ce901 --- /dev/null +++ b/app/integrations/[slug]/page.tsx @@ -0,0 +1,111 @@ +/** + * @file Dynamic route for individual integration guide pages. + * + * Handles all 50 integration routes via [slug]. + * Exports generateStaticParams for static generation and + * generateMetadata for per-page SEO (title, description, OG, JSON-LD). + */ + +import type { Metadata } from 'next' +import { notFound } from 'next/navigation' +import { integrations, getIntegration } from '@/lib/integrations' +import { getGuideContent } from '@/lib/integration-guides' +import { IntegrationGuide } from '@/components/IntegrationGuide' + +// * ─── Static Params ─────────────────────────────────────────────── +export function generateStaticParams() { + return integrations.map((i) => ({ slug: i.id })) +} + +// * ─── SEO Metadata ──────────────────────────────────────────────── +interface PageProps { + params: Promise<{ slug: string }> +} + +export async function generateMetadata({ params }: PageProps): Promise { + const { slug } = await params + const integration = getIntegration(slug) + if (!integration) return {} + + const title = `How to Add Pulse Analytics to ${integration.name} | Pulse by Ciphera` + const description = integration.seoDescription + const url = `https://pulse.ciphera.net/integrations/${integration.id}` + + return { + title, + description, + keywords: [ + `${integration.name} analytics`, + `${integration.name} Pulse`, + 'privacy-first analytics', + 'website analytics', + 'Ciphera Pulse', + integration.name, + ], + alternates: { canonical: url }, + openGraph: { + title, + description, + url, + siteName: 'Pulse by Ciphera', + type: 'article', + }, + twitter: { + card: 'summary', + title, + description, + }, + } +} + +// * ─── Page Component ────────────────────────────────────────────── +export default async function IntegrationPage({ params }: PageProps) { + const { slug } = await params + const integration = getIntegration(slug) + if (!integration) return notFound() + + const content = getGuideContent(slug) + if (!content) return notFound() + + // * HowTo JSON-LD for rich search snippets + const jsonLd = { + '@context': 'https://schema.org', + '@type': 'HowTo', + name: `How to Add Pulse Analytics to ${integration.name}`, + description: integration.seoDescription, + step: [ + { + '@type': 'HowToStep', + name: `Open your ${integration.name} project`, + text: `Navigate to your ${integration.name} project and locate the file where you manage the HTML head or layout.`, + }, + { + '@type': 'HowToStep', + name: 'Add the Pulse tracking script', + text: 'Insert the Pulse analytics script tag with your data-domain attribute into the head section of your page.', + }, + { + '@type': 'HowToStep', + name: 'Deploy and verify', + text: 'Deploy your changes and visit your Pulse dashboard to verify that page views are being recorded.', + }, + ], + tool: { + '@type': 'HowToTool', + name: 'Pulse by Ciphera', + url: 'https://pulse.ciphera.net', + }, + } + + return ( + <> + - - - - -`} - - -

Method 2: angular.json Scripts Array

-

- Alternatively, reference an external script in your angular.json build configuration. However, for analytics scripts that need defer and data-* attributes, Method 1 is simpler and recommended. -

- -

Configuration Options

-
    -
  • - data-domain: The domain name you added to your Pulse dashboard (e.g., example.com). -
  • -
  • - defer: Ensures the script loads without blocking the page. -
  • -
- - ) -} diff --git a/app/integrations/astro/page.tsx b/app/integrations/astro/page.tsx deleted file mode 100644 index 004c687..0000000 --- a/app/integrations/astro/page.tsx +++ /dev/null @@ -1,60 +0,0 @@ -'use client' - -import { IntegrationGuide } from '@/components/IntegrationGuide' -import { CodeBlock } from '@/components/CodeBlock' -import { getIntegration } from '@/lib/integrations' -import { notFound } from 'next/navigation' - -export default function AstroIntegrationPage() { - const integration = getIntegration('astro') - if (!integration) return notFound() - - return ( - -

- Astro makes it easy to add third-party scripts. Drop the Pulse snippet into your base layout and you're done. -

- -
- -

Base Layout (Recommended)

-

- Add the script to the <head> of your base layout file so it loads on every page. -

- - -{`--- -// Base layout used by all pages ---- - - - - - - - - - My Astro Site - - - - -`} - - -

Using Astro's Script Integration

-

- You can also configure the script in your astro.config.mjs using the injectScript API of an Astro integration, but the layout approach above is simpler for most projects. -

- -

Astro + View Transitions

-

- If you use Astro's View Transitions, the Pulse script persists across navigations automatically since it is loaded in the <head> with defer. -

-
- ) -} diff --git a/app/integrations/gatsby/page.tsx b/app/integrations/gatsby/page.tsx deleted file mode 100644 index 5191b88..0000000 --- a/app/integrations/gatsby/page.tsx +++ /dev/null @@ -1,65 +0,0 @@ -'use client' - -import { IntegrationGuide } from '@/components/IntegrationGuide' -import { CodeBlock } from '@/components/CodeBlock' -import { getIntegration } from '@/lib/integrations' -import { notFound } from 'next/navigation' - -export default function GatsbyIntegrationPage() { - const integration = getIntegration('gatsby') - if (!integration) return notFound() - - return ( - -

- Add Pulse to your Gatsby site using the gatsby-ssr API or the Gatsby Head API. -

- -
- -

Method 1: gatsby-ssr.js (Recommended)

-

- Use the onRenderBody API to inject the script into every page's <head>. -

- - -{`import React from "react" - -export const onRenderBody = ({ setHeadComponents }) => { - setHeadComponents([ - `} - - -
    -
  1. Click Save.
  2. -
- -

Theme-Level Integration (Alternative)

-

- If you prefer, you can also add the script directly to your Ghost theme's default.hbs file, just before the closing </head> tag. This approach requires re-uploading the theme whenever you make changes. -

- -

Important Notes

-
    -
  • - data-domain: Use your publication's domain (e.g., blog.example.com). -
  • -
  • - Code Injection is available on all Ghost plans, including the free self-hosted version. -
  • -
-
- ) -} diff --git a/app/integrations/hugo/page.tsx b/app/integrations/hugo/page.tsx deleted file mode 100644 index 2aa0b6f..0000000 --- a/app/integrations/hugo/page.tsx +++ /dev/null @@ -1,65 +0,0 @@ -'use client' - -import { IntegrationGuide } from '@/components/IntegrationGuide' -import { CodeBlock } from '@/components/CodeBlock' -import { getIntegration } from '@/lib/integrations' -import { notFound } from 'next/navigation' - -export default function HugoIntegrationPage() { - const integration = getIntegration('hugo') - if (!integration) return notFound() - - return ( - -

- Add Pulse to your Hugo site by placing the script in a partial or directly in your base template. -

- -
- -

Method 1: Partial (Recommended)

-

- Create an analytics partial and include it in your base template's <head>. -

- - -{`{{ if not .Site.IsServer }} - -{{ end }}`} - - -

- Then include the partial in your baseof.html: -

- - -{` - - - - - {{ .Title }} - - {{ partial "analytics.html" . }} - - - {{ block "main" . }}{{ end }} - -`} - - -

Method 2: Direct Insertion

-

- If you prefer, add the script tag directly to the <head> of your baseof.html without creating a partial. -

- -

- The if not .Site.IsServer guard ensures the script is excluded during local development with hugo server. -

-
- ) -} diff --git a/app/integrations/nextjs/page.tsx b/app/integrations/nextjs/page.tsx deleted file mode 100644 index 5704ab3..0000000 --- a/app/integrations/nextjs/page.tsx +++ /dev/null @@ -1,87 +0,0 @@ -'use client' - -import { IntegrationGuide } from '@/components/IntegrationGuide' -import { CodeBlock } from '@/components/CodeBlock' -import { getIntegration } from '@/lib/integrations' -import { notFound } from 'next/navigation' - -export default function NextJsIntegrationPage() { - const integration = getIntegration('nextjs') - if (!integration) return notFound() - - return ( - -

- The best way to add Pulse to your Next.js application is using the built-in next/script component. -

- -
- -

Using App Router (Recommended)

-

- Add the script to your root layout file (usually app/layout.tsx or app/layout.js). -

- - -{`import Script from 'next/script' - -export default function RootLayout({ - children, -}: { - children: React.ReactNode -}) { - return ( - - - - - My React App - - -
- -`} -
- -

Method 2: Programmatic Injection

-

- If you need to load the script dynamically (e.g., only in production), you can use a useEffect hook in your main App component. -

- - -{`import { useEffect } from 'react' - -function App() { - useEffect(() => { - // Only load in production - if (process.env.NODE_ENV === 'production') { - const script = document.createElement('script') - script.defer = true - script.setAttribute('data-domain', 'your-site.com') - script.src = 'https://pulse.ciphera.net/script.js' - document.head.appendChild(script) - } - }, []) - - return ( -
-

Hello World

-
- ) -}`} -
-
- ) -} diff --git a/app/integrations/remix/page.tsx b/app/integrations/remix/page.tsx deleted file mode 100644 index a7d7bcc..0000000 --- a/app/integrations/remix/page.tsx +++ /dev/null @@ -1,71 +0,0 @@ -'use client' - -import { IntegrationGuide } from '@/components/IntegrationGuide' -import { CodeBlock } from '@/components/CodeBlock' -import { getIntegration } from '@/lib/integrations' -import { notFound } from 'next/navigation' - -export default function RemixIntegrationPage() { - const integration = getIntegration('remix') - if (!integration) return notFound() - - return ( - -

- Add Pulse to your Remix application by placing the script tag in your root route's <head>. -

- -
- -

Root Route (Recommended)

-

- In Remix, the app/root.tsx file controls the HTML shell. Add the Pulse script inside the <head> section. -

- - -{`import { - Links, - Meta, - Outlet, - Scripts, - ScrollRestoration, -} from "@remix-run/react" - -export default function App() { - return ( - - - - - - - - {/* Pulse Analytics */} - `} - - -

Method 2: Custom Pixels (Shopify Plus)

-

- If you are on Shopify Plus, you can also use Customer Events > Custom Pixels to add the script. Go to Settings > Customer events and create a new custom pixel. -

- -

Important Notes

-
    -
  • - data-domain: Use your custom domain (e.g., shop.example.com) if you have one, or your .myshopify.com domain. -
  • -
  • - Pulse does not use cookies and is fully GDPR-compliant — no cookie banner changes needed. -
  • -
-
- ) -} diff --git a/app/integrations/squarespace/page.tsx b/app/integrations/squarespace/page.tsx deleted file mode 100644 index a428a61..0000000 --- a/app/integrations/squarespace/page.tsx +++ /dev/null @@ -1,52 +0,0 @@ -'use client' - -import { IntegrationGuide } from '@/components/IntegrationGuide' -import { CodeBlock } from '@/components/CodeBlock' -import { getIntegration } from '@/lib/integrations' -import { notFound } from 'next/navigation' - -export default function SquarespaceIntegrationPage() { - const integration = getIntegration('squarespace') - if (!integration) return notFound() - - return ( - -

- Add Pulse to your Squarespace site using the built-in Code Injection feature — no plugins needed. -

- -
- -

Code Injection (Recommended)

-
    -
  1. In your Squarespace dashboard, go to Settings > Developer Tools > Code Injection.
  2. -
  3. In the Header field, paste the following snippet:
  4. -
- - -{``} - - -
    -
  1. Click Save.
  2. -
- -

Important Notes

-
    -
  • - Code Injection is available on Squarespace Business and Commerce plans. -
  • -
  • - data-domain: Use your custom domain (e.g., example.com) rather than the .squarespace.com subdomain. -
  • -
  • - Pulse is cookie-free, so you do not need to update your Squarespace cookie banner settings. -
  • -
-
- ) -} diff --git a/app/integrations/svelte/page.tsx b/app/integrations/svelte/page.tsx deleted file mode 100644 index b123581..0000000 --- a/app/integrations/svelte/page.tsx +++ /dev/null @@ -1,70 +0,0 @@ -'use client' - -import { IntegrationGuide } from '@/components/IntegrationGuide' -import { CodeBlock } from '@/components/CodeBlock' -import { getIntegration } from '@/lib/integrations' -import { notFound } from 'next/navigation' - -export default function SvelteIntegrationPage() { - const integration = getIntegration('svelte') - if (!integration) return notFound() - - return ( - -

- Integrating Pulse with Svelte or SvelteKit takes less than a minute. Just add the script tag to your HTML entry point. -

- -
- -

Svelte (Vite)

-

- For a standard Svelte project scaffolded with Vite, add the script to the <head> of your index.html. -

- - -{` - - - - - - - - - My Svelte App - - -
- - -`} -
- -

SvelteKit

-

- In SvelteKit, add the script to your root layout's <svelte:head> block so it loads on every page. -

- - -{` - - - -`} - - -

- Alternatively, you can add the script to src/app.html directly in the <head> section. -

-
- ) -} diff --git a/app/integrations/vue/page.tsx b/app/integrations/vue/page.tsx deleted file mode 100644 index c9278df..0000000 --- a/app/integrations/vue/page.tsx +++ /dev/null @@ -1,53 +0,0 @@ -'use client' - -import { IntegrationGuide } from '@/components/IntegrationGuide' -import { CodeBlock } from '@/components/CodeBlock' -import { getIntegration } from '@/lib/integrations' -import { notFound } from 'next/navigation' - -export default function VueIntegrationPage() { - const integration = getIntegration('vue') - if (!integration) return notFound() - - return ( - -

- Integrating Pulse with Vue.js is straightforward. Add the script to your index.html file. -

- -
- -

index.html (Vue CLI & Vite)

-

- Add the script tag to the <head> section of your index.html file. This works for both Vue 2 and Vue 3 projects created with Vue CLI or Vite. -

- - -{` - - - - - - - - - My Vue App - - -
- - -`} -
- -

- Looking for Nuxt.js? Check the dedicated Nuxt integration guide. -

-
- ) -} diff --git a/app/integrations/webflow/page.tsx b/app/integrations/webflow/page.tsx deleted file mode 100644 index a6a3e93..0000000 --- a/app/integrations/webflow/page.tsx +++ /dev/null @@ -1,55 +0,0 @@ -'use client' - -import { IntegrationGuide } from '@/components/IntegrationGuide' -import { CodeBlock } from '@/components/CodeBlock' -import { getIntegration } from '@/lib/integrations' -import { notFound } from 'next/navigation' - -export default function WebflowIntegrationPage() { - const integration = getIntegration('webflow') - if (!integration) return notFound() - - return ( - -

- Add Pulse to your Webflow site by pasting a single snippet into your project's custom code settings. -

- -
- -

Project-Level Custom Code (Recommended)

-
    -
  1. Open your Webflow project and go to Project Settings.
  2. -
  3. Navigate to the Custom Code tab.
  4. -
  5. In the Head Code section, paste the following snippet:
  6. -
- - -{``} - - -
    -
  1. Click Save Changes and publish your site.
  2. -
- -

Page-Level Custom Code

-

- If you only want to track specific pages, you can add the script to individual page settings instead of the project-level settings. Go to the page's settings panel and paste the snippet in the Head Code section. -

- -

Important Notes

-
    -
  • - Custom code requires a Webflow paid site plan (Basic or higher). -
  • -
  • - The script will not appear in the Webflow Designer preview — publish the site and view the live version to verify. -
  • -
-
- ) -} diff --git a/app/integrations/wix/page.tsx b/app/integrations/wix/page.tsx deleted file mode 100644 index db49e28..0000000 --- a/app/integrations/wix/page.tsx +++ /dev/null @@ -1,54 +0,0 @@ -'use client' - -import { IntegrationGuide } from '@/components/IntegrationGuide' -import { CodeBlock } from '@/components/CodeBlock' -import { getIntegration } from '@/lib/integrations' -import { notFound } from 'next/navigation' - -export default function WixIntegrationPage() { - const integration = getIntegration('wix') - if (!integration) return notFound() - - return ( - -

- Add Pulse to your Wix site using the Custom Code feature in your site settings. -

- -
- -

Custom Code (Recommended)

-
    -
  1. In your Wix dashboard, go to Settings > Custom Code (under “Advanced”).
  2. -
  3. Click + Add Custom Code.
  4. -
  5. Paste the following snippet:
  6. -
- - -{``} - - -
    -
  1. Set the code to load in the Head of All pages.
  2. -
  3. Click Apply.
  4. -
- -

Important Notes

-
    -
  • - Custom Code requires a Wix Premium plan with a connected domain. -
  • -
  • - data-domain: Use your connected custom domain, not the .wixsite.com subdomain. -
  • -
  • - Pulse is cookie-free and GDPR-compliant — no consent banner changes are needed. -
  • -
-
- ) -} diff --git a/app/integrations/wordpress/page.tsx b/app/integrations/wordpress/page.tsx deleted file mode 100644 index c8d9a6b..0000000 --- a/app/integrations/wordpress/page.tsx +++ /dev/null @@ -1,46 +0,0 @@ -'use client' - -import { IntegrationGuide } from '@/components/IntegrationGuide' -import { CodeBlock } from '@/components/CodeBlock' -import { getIntegration } from '@/lib/integrations' -import { notFound } from 'next/navigation' - -export default function WordPressIntegrationPage() { - const integration = getIntegration('wordpress') - if (!integration) return notFound() - - return ( - -

- You can add Pulse to your WordPress site without installing any heavy plugins, or by using a simple code snippet plugin. -

- -
- -

Method 1: Using a Plugin (Easiest)

-
    -
  1. Install a plugin like “Insert Headers and Footers” (WPCode).
  2. -
  3. Go to the plugin settings and find the “Scripts in Header” section.
  4. -
  5. Paste the following code snippet:
  6. -
- - -{``} - - -

Method 2: Edit Theme Files (Advanced)

-

- If you are comfortable editing your theme files, you can add the script directly to your header.php file. -

-
    -
  1. Go to Appearance > Theme File Editor.
  2. -
  3. Select header.php from the right sidebar.
  4. -
  5. Paste the script tag just before the closing </head> tag.
  6. -
-
- ) -} diff --git a/components/CodeBlock.tsx b/components/CodeBlock.tsx index 48f4c81..8379a41 100644 --- a/components/CodeBlock.tsx +++ b/components/CodeBlock.tsx @@ -1,5 +1,3 @@ -'use client' - /** * @file Reusable code block component for integration guide pages. * diff --git a/components/IntegrationGuide.tsx b/components/IntegrationGuide.tsx index 236fb8e..e78c408 100644 --- a/components/IntegrationGuide.tsx +++ b/components/IntegrationGuide.tsx @@ -1,16 +1,14 @@ -'use client' - /** * @file Shared layout component for individual integration guide pages. * * Provides the background atmosphere, back-link, header (logo + title), - * and prose-styled content area used by every integration sub-page. + * prose-styled content area, and a related integrations section. */ import Link from 'next/link' -import { ArrowLeftIcon } from '@ciphera-net/ui' +import { ArrowLeftIcon, ArrowRightIcon } from '@ciphera-net/ui' import { type ReactNode } from 'react' -import { type Integration } from '@/lib/integrations' +import { type Integration, getIntegration } from '@/lib/integrations' interface IntegrationGuideProps { /** Integration metadata (name, icon, etc.) */ @@ -20,7 +18,8 @@ interface IntegrationGuideProps { } /** - * Renders the full-page layout for a single integration guide. + * Renders the full-page layout for a single integration guide, + * including related integrations at the bottom. */ export function IntegrationGuide({ integration, children }: IntegrationGuideProps) { // * Scale the icon up for the detail-page header (w-10 h-10) @@ -30,6 +29,12 @@ export function IntegrationGuide({ integration, children }: IntegrationGuideProp
) + // * Resolve related integrations from IDs + const relatedIntegrations = integration.relatedIds + .map((id) => getIntegration(id)) + .filter((i): i is Integration => i !== undefined) + .slice(0, 4) + return (
{/* * --- ATMOSPHERE (Background) --- */} @@ -63,6 +68,37 @@ export function IntegrationGuide({ integration, children }: IntegrationGuideProp
{children}
+ + {/* * --- Related Integrations --- */} + {relatedIntegrations.length > 0 && ( +
+

+ Related Integrations +

+
+ {relatedIntegrations.map((related) => ( + +
+ {related.icon} +
+
+ + {related.name} + + + {related.description} + +
+ + + ))} +
+
+ )}
) diff --git a/lib/integration-guides.tsx b/lib/integration-guides.tsx new file mode 100644 index 0000000..94b11be --- /dev/null +++ b/lib/integration-guides.tsx @@ -0,0 +1,2632 @@ +/** + * @file Integration guide content — full JSX guide for every supported platform. + * + * Each guide is keyed by the integration slug and rendered on the + * `/integrations/[slug]` page. + * + * * 50 guides across 7 categories. + */ + +import { type ReactNode } from 'react' +import { CodeBlock } from '@/components/CodeBlock' + +// * ─── Guide registry ───────────────────────────────────────────────────────── + +const guides: Record = { + /* ──────────────────────────────────────────────────────────────────────────── + * 1. Next.js + * ──────────────────────────────────────────────────────────────────────────── */ + 'nextjs': ( + <> +

+ The best way to add Pulse to your Next.js application is using the + built-in next/script component. +

+ +
+ +

Method 1: App Router

+

+ Add the Pulse script to your root layout so it loads on every page. +

+ {`import Script from 'next/script' + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + + My React App + + +
+ +`}
+ +

Method 2: Programmatic injection via useEffect

+

+ If you prefer to inject the script programmatically (e.g. only in + production), use a useEffect hook. +

+ {`import { useEffect } from 'react' + +function App() { + useEffect(() => { + if (process.env.NODE_ENV === 'production') { + const script = document.createElement('script') + script.defer = true + script.setAttribute('data-domain', 'your-site.com') + script.src = 'https://pulse.ciphera.net/script.js' + document.head.appendChild(script) + } + }, []) + + return

Hello World

+}`}
+ +

+ Related Integrations:{' '} + Next.js,{' '} + Remix,{' '} + Gatsby,{' '} + Preact +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 3. Vue.js + * ──────────────────────────────────────────────────────────────────────────── */ + 'vue': ( + <> +

+ Add the script to your index.html — works for both Vue CLI + and Vite-based projects. +

+ +
+ +

Add the Pulse script to index.html

+

+ Both Vue CLI and Vite use an index.html as the entry point. + Simply add the Pulse script inside the <head> tag. +

+ {` + + + + + + + + My Vue App + + +
+ + +`}
+ +

+ Looking for Nuxt? Check the dedicated{' '} + Nuxt guide. +

+ +

+ Related Integrations:{' '} + Nuxt,{' '} + VitePress +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 4. Angular + * ──────────────────────────────────────────────────────────────────────────── */ + 'angular': ( + <> +

+ Add the script to your src/index.html — the single entry + point for all Angular applications. +

+ +
+ +

Add the Pulse script to index.html

+

+ Place the Pulse script inside the <head> tag of your + Angular app's src/index.html. +

+ {` + + + + + + + + My Angular App + + + + + +`} + +

+ For more details, see the{' '} + + Angular docs + . +

+ +

+ Related Integrations:{' '} + React,{' '} + Vue.js +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 5. Svelte + * ──────────────────────────────────────────────────────────────────────────── */ + 'svelte': ( + <> +

+ Add the script to your index.html for Vite-based Svelte, or + use <svelte:head> in SvelteKit. +

+ +
+ +

Method 1: Svelte (Vite)

+

+ For standard Svelte projects using Vite, add the Pulse script to your{' '} + index.html. +

+ {` + + + + + + + + My Svelte App + + +
+ + +`}
+ +

Method 2: SvelteKit

+

+ In SvelteKit, use <svelte:head> in your root layout + to add the script to every page. +

+ {` + + + +`} + +

+ Alternatively, you can add the script directly to{' '} + src/app.html in your SvelteKit project. +

+ +

+ Related Integrations:{' '} + Astro,{' '} + Vue.js +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 6. Nuxt + * ──────────────────────────────────────────────────────────────────────────── */ + 'nuxt': ( + <> +

+ Configure Pulse analytics in your nuxt.config for a + framework-native setup. +

+ +
+ +

Method 1: Nuxt 3

+

+ Add the Pulse script via the app.head option in your Nuxt 3 + config. +

+ {`export default defineNuxtConfig({ + app: { + head: { + script: [ + { + defer: true, + 'data-domain': 'your-site.com', + src: 'https://pulse.ciphera.net/script.js', + }, + ], + }, + }, +})`} + +

Method 2: Nuxt 2

+

+ In Nuxt 2, use the head property in your config. +

+ {`export default { + head: { + script: [ + { + defer: true, + 'data-domain': 'your-site.com', + src: 'https://pulse.ciphera.net/script.js', + }, + ], + }, +}`} + +

+ For more details, see the{' '} + + Nuxt head config docs + . +

+ +

+ Related Integrations:{' '} + Vue.js,{' '} + Next.js,{' '} + VitePress +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 7. Remix + * ──────────────────────────────────────────────────────────────────────────── */ + 'remix': ( + <> +

+ Add the Pulse script to your app/root.tsx so it's + included on every route. +

+ +
+ +

Add script to app/root.tsx

+

+ The root route is the top-level layout in Remix. Add the Pulse script + inside the <head> section. +

+ {`import { + Links, + Meta, + Outlet, + Scripts, + ScrollRestoration, +} from '@remix-run/react' + +export default function App() { + return ( + + + + + + + + + {title} + + + + +`} + +

+ If you're using Astro's View Transitions, the script will + persist across navigations by default since it's in the{' '} + <head>. +

+ +

+ For more details, see the{' '} + + Astro scripts docs + . +

+ +

+ Related Integrations:{' '} + Svelte,{' '} + Hugo,{' '} + Eleventy +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 9. Solid.js + * ──────────────────────────────────────────────────────────────────────────── */ + 'solidjs': ( + <> +

+ Add the Pulse script to your index.html like any Vite-based + project. +

+ +
+ +

Add the Pulse script to index.html

+

+ Solid.js uses Vite under the hood, so simply add the Pulse script to + your HTML entry file. +

+ {` + + + + + + + + My Solid App + + +
+ + +`}
+ +

+ Related Integrations:{' '} + React,{' '} + Qwik,{' '} + Preact +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 10. Qwik + * ──────────────────────────────────────────────────────────────────────────── */ + 'qwik': ( + <> +

+ Add the Pulse script to your root entry component so it loads on every + page. +

+ +
+ +

Add to src/root.tsx

+

+ In Qwik, add the Pulse script to the <head> section + of your root component. +

+ {`import { component$ } from '@builder.io/qwik' +import { QwikCityProvider, RouterOutlet } from '@builder.io/qwik-city' + +export default component$(() => { + return ( + + + + + + + My Preact App + + +
+ + +`}
+ +

+ Related Integrations:{' '} + React,{' '} + Solid.js +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 12. HTMX + * ──────────────────────────────────────────────────────────────────────────── */ + 'htmx': ( + <> +

+ Since HTMX is used with server-rendered HTML, add the Pulse script to + your server's base template. +

+ +
+ +

Add to your base HTML template

+

+ HTMX works with any backend — Django, Flask, Laravel, Rails, Express, + and more. Add the Pulse script to whichever base template your server + renders. +

+ {` + + + + + + + + + My HTMX App + + + + +`} + +

+ See the backend-specific guides for template syntax details:{' '} + Django,{' '} + Flask,{' '} + Laravel,{' '} + Rails,{' '} + Express. +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 13. Ember.js + * ──────────────────────────────────────────────────────────────────────────── */ + 'ember': ( + <> +

+ Add the Pulse script to your app/index.html. +

+ +
+ +

Add the Pulse script to index.html

+

+ Ember uses app/index.html as its entry point. Add the + script inside the <head> tag. +

+ {` + + + + + + + + My Ember App + + {{content-for "head"}} + + + + {{content-for "body"}} + + + +`} + +

+ For more details, see the{' '} + + Ember docs + . +

+ +

+ Related Integrations:{' '} + React,{' '} + Angular +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 14. Laravel + * ──────────────────────────────────────────────────────────────────────────── */ + 'laravel': ( + <> +

+ Add the Pulse script to your Blade layout template with a production + guard. +

+ +
+ +

Add to your Blade layout

+

+ Use Laravel's @production directive to only load the + script in production. +

+ {` + + + + + + @production + + @endproduction + + @yield('title') + @vite(['resources/css/app.css', 'resources/js/app.js']) + + + @yield('content') + +`} + +

+ For more details, see the{' '} + + Laravel @production docs + . +

+ +

+ Related Integrations:{' '} + Django,{' '} + Rails,{' '} + WordPress +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 15. Django + * ──────────────────────────────────────────────────────────────────────────── */ + 'django': ( + <> +

+ Add the Pulse script to your base template with a debug guard. +

+ +
+ +

Add to your base template

+

+ Use Django's template tags to only load the script when{' '} + DEBUG is False. +

+ {` + + + + + + {% if not debug %} + + {% endif %} + + {% block title %}My Django App{% endblock %} + + + {% block content %}{% endblock %} + +`} + +

+ Make sure to pass debug to the template context via{' '} + settings.DEBUG, or use a context processor to make it + available globally. +

+ +

+ For more details, see the{' '} + + Django template docs + . +

+ +

+ Related Integrations:{' '} + Flask,{' '} + Laravel,{' '} + HTMX +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 16. Ruby on Rails + * ──────────────────────────────────────────────────────────────────────────── */ + 'rails': ( + <> +

+ Add the Pulse script to your application layout with a production + environment guard. +

+ +
+ +

Add to your application layout

+

+ Use an if guard to only load the script in production. +

+ {` + + + + + + <% if Rails.env.production? %> + + <% end %> + + <%= yield(:title) || "My Rails App" %> + <%= csrf_meta_tags %> + <%= stylesheet_link_tag "application" %> + + + <%= yield %> + +`} + +

+ For more details, see the{' '} + + Rails layout docs + . +

+ +

+ Related Integrations:{' '} + Laravel,{' '} + Django,{' '} + Jekyll +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 17. Flask + * ──────────────────────────────────────────────────────────────────────────── */ + 'flask': ( + <> +

+ Add the Pulse script to your Jinja2 base template with a debug guard. +

+ +
+ +

Add to your base template

+

+ Use Jinja2's conditional to only load the script when{' '} + DEBUG is off. +

+ {` + + + + + + {% if not config.DEBUG %} + + {% endif %} + + {% block title %}My Flask App{% endblock %} + + + {% block content %}{% endblock %} + +`} + +

+ For more details, see the{' '} + + Flask template docs + . +

+ +

+ Related Integrations:{' '} + Django,{' '} + HTMX,{' '} + Express +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 18. Express + * ──────────────────────────────────────────────────────────────────────────── */ + 'express': ( + <> +

+ Add the Pulse script to your template engine's layout (EJS, Pug, + Handlebars) or serve it via static HTML. +

+ +
+ +

Method 1: EJS template

+

+ If you use EJS as your template engine, add the script to your layout + with a production guard. +

+ {` + + + + + + <% if (process.env.NODE_ENV === 'production') { %> + + <% } %> + + <%= title %> + + + <%- body %> + +`} + +

Method 2: Static HTML

+

+ If you serve static HTML files via Express, add the script directly. +

+ {` + + + + + + + + My Express App + + +

Hello World

+ +`}
+ +

+ Related Integrations:{' '} + Flask,{' '} + Next.js,{' '} + React +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 19. Gatsby + * ──────────────────────────────────────────────────────────────────────────── */ + 'gatsby': ( + <> +

+ Use the Gatsby SSR API or the Gatsby Head API to add Pulse to your site. +

+ +
+ +

Method 1: gatsby-ssr.js

+

+ Use the onRenderBody hook to inject the Pulse script into + every page's <head>. +

+ {`import React from "react" + +export const onRenderBody = ({ setHeadComponents }) => { + setHeadComponents([ + +{{ end }}`} + +

Method 2: Include the partial in your base layout

+

+ Add the partial to your baseof.html layout. +

+ {` + + + + + {{ partial "analytics.html" . }} + {{ .Title }} + + + {{ block "main" . }}{{ end }} + +`} + +

+ For more details, see the{' '} + + Hugo partials docs + . +

+ +

+ Related Integrations:{' '} + Jekyll,{' '} + Eleventy,{' '} + Astro +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 21. Eleventy + * ──────────────────────────────────────────────────────────────────────────── */ + 'eleventy': ( + <> +

+ Add the Pulse script to your base Nunjucks or Liquid layout. +

+ +
+ +

Add to your base layout

+

+ Place the Pulse script inside the <head> of your + shared base template. +

+ {` + + + + + + + + {{ title }} + + + {{ content | safe }} + +`} + +

+ For more details, see the{' '} + + Eleventy layouts docs + . +

+ +

+ Related Integrations:{' '} + Hugo,{' '} + Jekyll,{' '} + Astro +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 22. Jekyll + * ──────────────────────────────────────────────────────────────────────────── */ + 'jekyll': ( + <> +

+ Add the Pulse script to your default layout or an _includes{' '} + partial. +

+ +
+ +

Method 1: Create an analytics include

+

+ Create a reusable include with a production environment guard. +

+ {`{% if jekyll.environment == "production" %} + +{% endif %}`} + +

Method 2: Include in your default layout

+

+ Reference the include in your default layout's{' '} + <head>. +

+ {` + + + + + {% include analytics.html %} + {{ page.title }} + + + {{ content }} + +`} + +

+ For more details, see the{' '} + + Jekyll includes docs + . +

+ +

+ Related Integrations:{' '} + Hugo,{' '} + Eleventy,{' '} + GitHub Pages +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 23. Docusaurus + * ──────────────────────────────────────────────────────────────────────────── */ + 'docusaurus': ( + <> +

+ Add the Pulse script via the scripts array in your + Docusaurus config. +

+ +
+ +

Configure in docusaurus.config.js

+

+ Docusaurus supports a scripts config option that injects + script tags into every page. +

+ {`module.exports = { + scripts: [ + { + src: 'https://pulse.ciphera.net/script.js', + defer: true, + 'data-domain': 'your-site.com', + }, + ], + // ... rest of config +}`} + +

+ For more details, see the{' '} + + Docusaurus scripts config docs + . +

+ +

+ Related Integrations:{' '} + VitePress,{' '} + MkDocs,{' '} + Gatsby +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 24. VitePress + * ──────────────────────────────────────────────────────────────────────────── */ + 'vitepress': ( + <> +

+ Add the Pulse script via VitePress's head config + option. +

+ +
+ +

Configure in .vitepress/config.ts

+

+ VitePress lets you inject tags into the <head> of + every page via the head array. +

+ {`import { defineConfig } from 'vitepress' + +export default defineConfig({ + head: [ + [ + 'script', + { + defer: '', + 'data-domain': 'your-site.com', + src: 'https://pulse.ciphera.net/script.js', + }, + ], + ], +})`} + +

+ For more details, see the{' '} + + VitePress head config docs + . +

+ +

+ Related Integrations:{' '} + Docusaurus,{' '} + Vue.js,{' '} + Nuxt +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 25. Hexo + * ──────────────────────────────────────────────────────────────────────────── */ + 'hexo': ( + <> +

+ Add the Pulse script to your Hexo theme's layout file. +

+ +
+ +

Edit your theme's layout

+

+ Open the layout file for your active theme and add the Pulse script + inside the <head>. +

+ {` + + + + + + + + <%= config.title %> + <%- css('css/style') %> + + + <%- body %> + <%- js('js/script') %> + +`} + +

+ Alternatively, you can use Hexo's after_render filter + to inject the script programmatically. +

+ +

+ For more details, see the{' '} + + Hexo templates docs + . +

+ +

+ Related Integrations:{' '} + Hugo,{' '} + Jekyll,{' '} + Eleventy +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 26. MkDocs + * ──────────────────────────────────────────────────────────────────────────── */ + 'mkdocs': ( + <> +

+ Add the Pulse script to your MkDocs site using a custom template + override. +

+ +
+ +

Step 1: Create a custom template override

+

+ To include the data-domain attribute, create a template + override file. +

+ {`{% extends "base.html" %} + +{% block extrahead %} + +{% endblock %}`} + +

Step 2: Configure mkdocs.yml

+

+ Set the custom_dir to your overrides folder in your + MkDocs configuration. +

+ {`theme: + name: material + custom_dir: overrides`} + +

+ For more details, see the{' '} + + MkDocs customization docs + . +

+ +

+ Related Integrations:{' '} + Docusaurus,{' '} + VitePress,{' '} + Django +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 27. WordPress + * ──────────────────────────────────────────────────────────────────────────── */ + 'wordpress': ( + <> +

+ Add the Pulse script via a plugin or by editing your theme's header + file directly. +

+ +
+ +

Method 1: Using a plugin (Recommended)

+

+ The easiest way is to use the{' '} + + WPCode (Insert Headers and Footers) + {' '} + plugin. Install it, then go to Code Snippets → Header & Footer{' '} + and paste the Pulse script into the Header section. +

+ {``} + +

Method 2: Edit header.php directly

+

+ Go to Appearance → Theme File Editor and edit{' '} + header.php. Add the Pulse script before the closing{' '} + </head> tag. +

+ +

+ Related Integrations:{' '} + Ghost,{' '} + Drupal,{' '} + WooCommerce +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 28. Ghost + * ──────────────────────────────────────────────────────────────────────────── */ + 'ghost': ( + <> +

+ Use Ghost's built-in Code Injection feature to add the Pulse + script — no theme editing required. +

+ +
+ +

Add via Code Injection

+

+ Go to Settings → Code injection → Site Header{' '} + and paste the Pulse script. +

+ {``} + +

+ Alternatively, you can add the script directly to your theme's{' '} + default.hbs template file. +

+ +

+ For more details, see the{' '} + + Ghost themes docs + . +

+ +

+ Related Integrations:{' '} + WordPress,{' '} + Blogger +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 29. Drupal + * ──────────────────────────────────────────────────────────────────────────── */ + 'drupal': ( + <> +

+ Add the Pulse script via a contributed module or by editing your + theme's Twig template. +

+ +
+ +

Method 1: Using Asset Injector module

+

+ Install the{' '} + + Asset Injector + {' '} + module and create a new JS injector with the Pulse script. Set it to + load on all pages in the header region. +

+ +

Method 2: Edit html.html.twig

+

+ Add the script directly to your theme's{' '} + html.html.twig template in the head area. +

+ {` + + + + {{ head_title|safe_join(' | ') }} + + + + + + + {{ page_top }} + {{ page }} + {{ page_bottom }} + + +`} + +

+ For more details, see the{' '} + + Drupal theming docs + . +

+ +

+ Related Integrations:{' '} + WordPress,{' '} + Joomla +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 30. Joomla + * ──────────────────────────────────────────────────────────────────────────── */ + 'joomla': ( + <> +

+ Add the Pulse script via your Joomla template or a custom HTML module. +

+ +
+ +

Method 1: Edit template index.php

+

+ Go to Extensions → Templates → Your Template{' '} + and edit index.php. Add the Pulse script before the + closing </head> tag. +

+ {` + + + + + + + + + + +`} + +

Method 2: Custom HTML module

+

+ Create a “Custom HTML” module and assign it to the head + position of your template. Paste the Pulse script as the content. +

+ +

+ For more details, see the{' '} + + Joomla template docs + . +

+ +

+ Related Integrations:{' '} + WordPress,{' '} + Drupal +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 31. Strapi + * ──────────────────────────────────────────────────────────────────────────── */ + 'strapi': ( + <> +

+ Strapi is a headless CMS — add Pulse to the frontend{' '} + that consumes your Strapi API, not to Strapi itself. +

+ +
+ +

Where to add the script

+

+ Since Strapi is an API-only backend, the analytics script belongs in + your frontend application. Follow the guide for whichever framework + you're using to render your Strapi content: +

+ + +

+ Related Integrations:{' '} + Contentful,{' '} + Sanity,{' '} + Next.js +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 32. Sanity + * ──────────────────────────────────────────────────────────────────────────── */ + 'sanity': ( + <> +

+ Sanity is a headless CMS — add Pulse to the frontend{' '} + that renders your Sanity content, not to Sanity Studio. +

+ +
+ +

Where to add the script

+

+ Since Sanity is a headless content platform, the analytics script + belongs in your frontend application. Follow the guide for whichever + framework you're using: +

+ + +

+ Related Integrations:{' '} + Strapi,{' '} + Contentful,{' '} + Next.js +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 33. Contentful + * ──────────────────────────────────────────────────────────────────────────── */ + 'contentful': ( + <> +

+ Contentful is a headless CMS — add Pulse to the{' '} + frontend that displays your Contentful content. +

+ +
+ +

Where to add the script

+

+ Since Contentful is an API-first content platform, the analytics script + belongs in your frontend application. Follow the guide for your + framework: +

+ + +

+ Related Integrations:{' '} + Strapi,{' '} + Sanity,{' '} + Next.js +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 34. Payload CMS + * ──────────────────────────────────────────────────────────────────────────── */ + 'payload': ( + <> +

+ Payload CMS is a headless CMS — add Pulse to the{' '} + frontend application that renders your content. +

+ +
+ +

Where to add the script

+

+ Since Payload is a headless CMS, the analytics script belongs in your + frontend. Payload is most commonly used with Next.js, so the{' '} + Next.js guide{' '} + is the best starting point. +

+ + +

+ Related Integrations:{' '} + Strapi,{' '} + Contentful,{' '} + Next.js +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 35. Shopify + * ──────────────────────────────────────────────────────────────────────────── */ + 'shopify': ( + <> +

+ Add the Pulse script via the Shopify theme editor — no app needed. +

+ +
+ +

Method 1: Edit theme code

+

+ Go to Online Store → Themes → Edit code and + open layout/theme.liquid. Add the Pulse script before the + closing </head> tag. +

+ {` +`} + +

Method 2: Shopify Plus — Customer Events

+

+ If you're on Shopify Plus, you can add the Pulse script via{' '} + Settings → Customer Events → Custom Pixels. +

+ +

+ Use your custom domain or .myshopify.com domain as the{' '} + data-domain value. +

+ +

+ For more details, see the{' '} + + Shopify theme docs + . +

+ +

+ Related Integrations:{' '} + WooCommerce,{' '} + BigCommerce,{' '} + PrestaShop +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 36. WooCommerce + * ──────────────────────────────────────────────────────────────────────────── */ + 'woocommerce': ( + <> +

+ WooCommerce runs on WordPress. Use WordPress methods to add the Pulse + script. +

+ +
+ +

Method 1: Using a plugin (Recommended)

+

+ Install the{' '} + + WPCode plugin + {' '} + and paste the Pulse script into the Header section. +

+ {``} + +

Method 2: Edit header.php

+

+ Go to Appearance → Theme File Editor and add the + Pulse script to your theme's header.php before{' '} + </head>. +

+ +

+ Use the same domain you track your main WordPress site with. +

+ +

+ For the full WordPress setup, see the{' '} + WordPress guide. +

+ +

+ Related Integrations:{' '} + Shopify,{' '} + WordPress,{' '} + BigCommerce +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 37. BigCommerce + * ──────────────────────────────────────────────────────────────────────────── */ + 'bigcommerce': ( + <> +

+ Add the Pulse script via BigCommerce's Script Manager. +

+ +
+ +

Add via Script Manager

+

+ Go to Storefront → Script Manager → Create a Script{' '} + and configure it as follows: +

+
    +
  • Placement: Head
  • +
  • Location: All Pages
  • +
  • Script type: Script tag
  • +
+ {``} + +

+ For more details, see the{' '} + + BigCommerce Script Manager docs + . +

+ +

+ Related Integrations:{' '} + Shopify,{' '} + WooCommerce,{' '} + PrestaShop +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 38. PrestaShop + * ──────────────────────────────────────────────────────────────────────────── */ + 'prestashop': ( + <> +

+ Add the Pulse script to your PrestaShop theme template. +

+ +
+ +

Edit your theme's head template

+

+ Open the head partial for your active theme and add the Pulse script. +

+ {`{block name='head_seo'} + {$page.meta.title} + +{/block} + + + +{block name='head_stylesheets'} + {include file='_partials/stylesheets.tpl'} +{/block}`} + +

+ For more details, see the{' '} + + PrestaShop theme docs + . +

+ +

+ Related Integrations:{' '} + Shopify,{' '} + WooCommerce,{' '} + BigCommerce +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 39. Webflow + * ──────────────────────────────────────────────────────────────────────────── */ + 'webflow': ( + <> +

+ Paste the Pulse script into your Webflow project's custom code + settings. +

+ +
+ +

Add via Project Settings

+

+ Go to Project Settings → Custom Code → Head Code{' '} + and paste the Pulse script. +

+ {``} + +

+ Note: Custom code requires a paid Webflow site plan. The + script won't appear in the Designer preview — publish your site to + see it in action. +

+ +

+ For more details, see the{' '} + + Webflow custom code docs + . +

+ +

+ Related Integrations:{' '} + Squarespace,{' '} + Wix,{' '} + Framer +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 40. Squarespace + * ──────────────────────────────────────────────────────────────────────────── */ + 'squarespace': ( + <> +

+ Use Squarespace's Code Injection feature to add the Pulse script. +

+ +
+ +

Add via Code Injection

+

+ Go to Settings → Developer Tools → Code Injection → Header{' '} + and paste the Pulse script. +

+ {``} + +

+ Note: Code Injection requires a Business or Commerce + plan. +

+ +

+ For more details, see the{' '} + + Squarespace code injection docs + . +

+ +

+ Related Integrations:{' '} + Webflow,{' '} + Wix,{' '} + Carrd +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 41. Wix + * ──────────────────────────────────────────────────────────────────────────── */ + 'wix': ( + <> +

+ Use Wix's Custom Code settings to add the Pulse script. +

+ +
+ +

Add via Custom Code

+

+ Go to Settings → Custom Code → Add Custom Code. + Set the placement to Head and apply it to{' '} + All pages. +

+ {``} + +

+ Note: Custom Code requires a Wix Premium plan. +

+ +

+ For more details, see the{' '} + + Wix custom code docs + . +

+ +

+ Related Integrations:{' '} + Webflow,{' '} + Squarespace,{' '} + Framer +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 42. Framer + * ──────────────────────────────────────────────────────────────────────────── */ + 'framer': ( + <> +

+ Add the Pulse script to your Framer project's custom code settings. +

+ +
+ +

Add via Project Settings

+

+ Go to Project Settings → General → Custom Code → Head{' '} + and paste the Pulse script. +

+ {``} + +

+ For more details, see the{' '} + + Framer custom code docs + . +

+ +

+ Related Integrations:{' '} + Webflow,{' '} + Squarespace,{' '} + Wix +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 43. Carrd + * ──────────────────────────────────────────────────────────────────────────── */ + 'carrd': ( + <> +

+ Add the Pulse script to your Carrd site's head code section. +

+ +
+ +

Add via Settings

+

+ Open your site's Settings panel and navigate to the{' '} + Head section. Paste the Pulse script there. +

+ {``} + +

+ Note: Custom code requires a Carrd Pro plan. +

+ +

+ For more details, see the{' '} + + Carrd head settings docs + . +

+ +

+ Related Integrations:{' '} + Framer,{' '} + Webflow +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 44. Blogger + * ──────────────────────────────────────────────────────────────────────────── */ + 'blogger': ( + <> +

+ Add the Pulse script via Blogger's Theme HTML editor. +

+ +
+ +

Edit your theme HTML

+

+ Go to Theme → Edit HTML and paste the Pulse script + before the closing </head> tag. +

+ {` +`} + +

+ For more details, see the{' '} + + Blogger HTML editing docs + . +

+ +

+ Related Integrations:{' '} + WordPress,{' '} + Ghost +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 45. Google Tag Manager + * ──────────────────────────────────────────────────────────────────────────── */ + 'gtm': ( + <> +

+ Add Pulse via Google Tag Manager — works with any site that already has + GTM installed. +

+ +
+ +

Create a Custom HTML tag

+

Follow these steps to add Pulse through GTM:

+
    +
  1. Go to Tags → New → Custom HTML
  2. +
  3. Paste the Pulse script
  4. +
  5. Set the trigger to All Pages
  6. +
  7. Publish your container
  8. +
+ {``} + +

+ For more details, see the{' '} + + GTM Custom HTML tag docs + . +

+ +

+ Related Integrations:{' '} + WordPress,{' '} + Shopify,{' '} + Webflow +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 46. Notion + * ──────────────────────────────────────────────────────────────────────────── */ + 'notion': ( + <> +

+ Notion itself doesn't support custom scripts, but tools like{' '} + Super.so,{' '} + Potion, and{' '} + Feather{' '} + let you turn Notion pages into websites with custom code support. +

+ +
+ +

Super.so

+

+ Go to Site Settings → Code → Head and paste + the Pulse script. +

+ {``} + +

Potion

+

+ Go to Project Settings → Custom Code → Head{' '} + and paste the Pulse script. +

+ +

+ For more details, see the{' '} + + Super.so docs + . +

+ +

+ Related Integrations:{' '} + Webflow,{' '} + Framer,{' '} + Carrd +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 47. Cloudflare Pages + * ──────────────────────────────────────────────────────────────────────────── */ + 'cloudflare-pages': ( + <> +

+ Add Pulse to your project's HTML or follow your framework's + guide. +

+ +
+ +

Method 1: Framework-based setup (Recommended)

+

+ If you're deploying a framework (Next.js, Astro, Nuxt, etc.) to + Cloudflare Pages, follow that framework's integration guide: +

+ + +

Method 2: Static HTML

+

+ For static HTML sites, add the Pulse script directly to your{' '} + index.html. +

+ {` + + + + + + + + My Site + + +

Hello World

+ +`}
+ +

+ For more details, see the{' '} + + Cloudflare Pages docs + . +

+ +

+ Related Integrations:{' '} + Netlify,{' '} + Vercel,{' '} + GitHub Pages +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 48. Netlify + * ──────────────────────────────────────────────────────────────────────────── */ + 'netlify': ( + <> +

+ Add Pulse via your framework's setup or Netlify's snippet + injection feature. +

+ +
+ +

Method 1: Framework guide (Recommended)

+

+ The best approach is to follow your framework's integration guide. + Netlify deploys framework projects, so the script setup happens in your + source code: +

+ + +

Method 2: Snippet injection (via Netlify UI)

+

+ Go to Site settings → Build & deploy → Post processing → Snippet injection{' '} + and add the Pulse script to the <head> of your + pages. +

+ {``} + +

+ For more details, see the{' '} + + Netlify snippet injection docs + . +

+ +

+ Related Integrations:{' '} + Cloudflare Pages,{' '} + Vercel,{' '} + GitHub Pages +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 49. Vercel + * ──────────────────────────────────────────────────────────────────────────── */ + 'vercel': ( + <> +

+ Add Pulse via your framework's setup. Vercel deploys framework + projects, so the script is added in your source code. +

+ +
+ +

Follow your framework's guide

+

+ Vercel is a deployment platform — it doesn't have a built-in + mechanism for injecting scripts. Add the Pulse script following + your framework's integration guide: +

+ + +

+ Note: Vercel's Edge Middleware cannot inject + scripts by design. Use the framework-level approach instead. +

+ +

+ For more details, see the{' '} + + Vercel frameworks docs + . +

+ +

+ Related Integrations:{' '} + Netlify,{' '} + Cloudflare Pages,{' '} + Next.js +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 50. GitHub Pages + * ──────────────────────────────────────────────────────────────────────────── */ + 'github-pages': ( + <> +

+ Add the Pulse script to your static site's HTML template on GitHub + Pages. +

+ +
+ +

Method 1: Jekyll (default for GitHub Pages)

+

+ Create an analytics include with a production guard and reference it in + your default layout. +

+ {`{% if jekyll.environment == "production" %} + +{% endif %}`} + +

+ Then include it in your layout's <head>: +

+ {` + + + + + {% include analytics.html %} + {{ page.title }} + + + {{ content }} + +`} + +

Method 2: Plain HTML

+

+ For simple static sites, add the Pulse script directly to your{' '} + index.html. +

+ {` + + + + + + + + My Site + + +

Hello World

+ +`}
+ +

Method 3: Hugo on GitHub Pages

+

+ If you're using Hugo with GitHub Pages, follow the{' '} + Hugo guide. +

+ +

+ For more details, see the{' '} + + GitHub Pages docs + . +

+ +

+ Related Integrations:{' '} + Jekyll,{' '} + Hugo,{' '} + Netlify +

+ + ), +} + +// * ─── Public API ───────────────────────────────────────────────────────────── + +export function getGuideContent(slug: string): ReactNode | undefined { + return guides[slug] +} diff --git a/lib/integrations.tsx b/lib/integrations.tsx index 18aefd4..457c3dd 100644 --- a/lib/integrations.tsx +++ b/lib/integrations.tsx @@ -1,15 +1,25 @@ /** - * @file Integration metadata and official SVG logos. + * @file Integration registry — metadata, official SVG logos, and SEO data for + * every platform Pulse supports. * * ! SVG paths sourced from simple-icons (https://simpleicons.org). - * All icons use a 24×24 viewBox. + * ! All icons use a 24×24 viewBox. + * + * * 50 integrations across 7 categories. */ import { type ReactNode } from 'react' // * ─── Types ────────────────────────────────────────────────────────────────── -export type IntegrationCategory = 'framework' | 'cms' | 'ssg' | 'platform' +export type IntegrationCategory = + | 'framework' + | 'backend' + | 'ssg' + | 'cms' + | 'ecommerce' + | 'platform' + | 'hosting' export interface Integration { id: string @@ -22,28 +32,40 @@ export interface Integration { invertInDark?: boolean /** Official 24×24 SVG as a React node */ icon: ReactNode + /** URL to official documentation / website */ + officialUrl: string + /** SEO meta description for this integration's guide page */ + seoDescription: string + /** Related integration IDs for cross-linking */ + relatedIds: string[] } // * ─── Category labels (for UI grouping) ────────────────────────────────────── export const categoryLabels: Record = { - framework: 'Frameworks', + framework: 'JavaScript Frameworks', + backend: 'Backend Frameworks', + ssg: 'Static Sites & Documentation', cms: 'CMS & Blogging', - ssg: 'Static-Site Generators', - platform: 'Platforms & Builders', + ecommerce: 'eCommerce', + platform: 'Platforms & Tools', + hosting: 'Hosting & Deployment', } export const categoryOrder: IntegrationCategory[] = [ 'framework', - 'platform', + 'backend', 'ssg', 'cms', + 'ecommerce', + 'platform', + 'hosting', ] // * ─── Integration registry ────────────────────────────────────────────────── export const integrations: Integration[] = [ - // * ── Frameworks ─────────────────────────────────────────────────────────── + // * ─── JavaScript Frameworks ──────────────────────────────────────────────── { id: 'nextjs', name: 'Next.js', @@ -56,6 +78,10 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://nextjs.org/docs', + seoDescription: + 'Step-by-step guide to adding Pulse privacy-first analytics to your Next.js app with next/script. Covers App Router and Pages Router.', + relatedIds: ['react', 'vercel', 'nuxt'], }, { id: 'react', @@ -68,6 +94,10 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://react.dev', + seoDescription: + 'Integrate Pulse analytics with any React SPA — Create React App, Vite, or custom setups. Two easy methods.', + relatedIds: ['nextjs', 'remix', 'gatsby', 'preact'], }, { id: 'vue', @@ -80,6 +110,10 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://vuejs.org', + seoDescription: + 'Add Pulse privacy-first analytics to your Vue.js app. Works with Vue 2, Vue 3, Vue CLI, and Vite.', + relatedIds: ['nuxt', 'vitepress'], }, { id: 'angular', @@ -93,6 +127,10 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://angular.dev', + seoDescription: + 'Add Pulse analytics to your Angular application. Simple index.html setup for all Angular versions.', + relatedIds: ['react', 'vue'], }, { id: 'svelte', @@ -105,6 +143,10 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://svelte.dev', + seoDescription: + 'Add Pulse analytics to Svelte or SvelteKit. Simple setup for both Vite-based Svelte and SvelteKit applications.', + relatedIds: ['astro', 'vue'], }, { id: 'nuxt', @@ -117,6 +159,10 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://nuxt.com/docs', + seoDescription: + 'Configure Pulse analytics in Nuxt 2 or Nuxt 3 via nuxt.config. Simple, framework-native setup.', + relatedIds: ['vue', 'nextjs', 'vitepress'], }, { id: 'remix', @@ -130,6 +176,10 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://remix.run/docs', + seoDescription: + 'Add Pulse analytics to your Remix application via the root route. Simple script tag in app/root.tsx.', + relatedIds: ['react', 'nextjs'], }, { id: 'astro', @@ -142,7 +192,176 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://docs.astro.build', + seoDescription: + 'Integrate Pulse analytics with Astro. Add the script to your base layout for all pages.', + relatedIds: ['svelte', 'hugo', 'eleventy'], }, + { + id: 'solidjs', + name: 'Solid.js', + description: 'Add Pulse analytics to your Solid.js application.', + category: 'framework', + brandColor: '#2C4F7C', + icon: ( + + + + ), + officialUrl: 'https://www.solidjs.com/docs', + seoDescription: + 'Add Pulse analytics to your Solid.js application. Simple index.html script tag setup.', + relatedIds: ['react', 'qwik', 'preact'], + }, + { + id: 'qwik', + name: 'Qwik', + description: 'Integrate Pulse analytics with your Qwik application.', + category: 'framework', + brandColor: '#AC7EF4', + icon: ( + + + + ), + officialUrl: 'https://qwik.dev/docs', + seoDescription: + 'Integrate Pulse analytics with Qwik. Add the script to your root entry file.', + relatedIds: ['react', 'solidjs', 'astro'], + }, + { + id: 'preact', + name: 'Preact', + description: 'Add Pulse analytics to your lightweight Preact application.', + category: 'framework', + brandColor: '#673AB8', + icon: ( + + + + ), + officialUrl: 'https://preactjs.com/guide', + seoDescription: + 'Add Pulse analytics to your Preact application. Same simple setup as any Vite or HTML project.', + relatedIds: ['react', 'solidjs'], + }, + { + id: 'htmx', + name: 'HTMX', + description: 'Add Pulse analytics to your HTMX-powered site.', + category: 'framework', + brandColor: '#3366CC', + icon: ( + + + + ), + officialUrl: 'https://htmx.org/docs', + seoDescription: + 'Add Pulse analytics to your HTMX-powered site. Works with any backend serving HTML.', + relatedIds: ['django', 'flask', 'laravel', 'rails'], + }, + { + id: 'ember', + name: 'Ember.js', + description: 'Add Pulse analytics to your Ember.js application.', + category: 'framework', + brandColor: '#E04E39', + icon: ( + + + + ), + officialUrl: 'https://guides.emberjs.com', + seoDescription: + 'Add Pulse analytics to your Ember.js app. Simple index.html script tag setup.', + relatedIds: ['react', 'angular'], + }, + + // * ─── Backend Frameworks ─────────────────────────────────────────────────── + { + id: 'laravel', + name: 'Laravel', + description: 'Add Pulse analytics to your Laravel app via Blade templates.', + category: 'backend', + brandColor: '#FF2D20', + icon: ( + + + + ), + officialUrl: 'https://laravel.com/docs', + seoDescription: + 'Add Pulse analytics to your Laravel app. Blade template integration for all Laravel versions.', + relatedIds: ['django', 'rails', 'wordpress'], + }, + { + id: 'django', + name: 'Django', + description: 'Add Pulse analytics to your Django app via templates.', + category: 'backend', + brandColor: '#092E20', + icon: ( + + + + ), + officialUrl: 'https://docs.djangoproject.com', + seoDescription: + 'Add Pulse analytics to your Django app. Template-based integration for all Django versions.', + relatedIds: ['flask', 'laravel', 'htmx'], + }, + { + id: 'rails', + name: 'Ruby on Rails', + description: 'Add Pulse analytics to your Rails app via ERB layouts.', + category: 'backend', + brandColor: '#D30001', + icon: ( + + + + ), + officialUrl: 'https://guides.rubyonrails.org', + seoDescription: + 'Add Pulse analytics to your Ruby on Rails app. ERB layout integration.', + relatedIds: ['laravel', 'django', 'jekyll'], + }, + { + id: 'flask', + name: 'Flask', + description: 'Add Pulse analytics to your Flask app via Jinja2 templates.', + category: 'backend', + brandColor: '#3BABC3', + icon: ( + + + + ), + officialUrl: 'https://flask.palletsprojects.com', + seoDescription: + 'Add Pulse analytics to your Flask app. Jinja2 template integration.', + relatedIds: ['django', 'htmx', 'express'], + }, + { + id: 'express', + name: 'Express', + description: 'Serve Pulse analytics from your Express.js application.', + category: 'backend', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://expressjs.com', + seoDescription: + 'Serve Pulse analytics from your Express.js app. Middleware or template-based setup.', + relatedIds: ['flask', 'nextjs', 'react'], + }, + + // * ─── Static Sites & Documentation ───────────────────────────────────────── { id: 'gatsby', name: 'Gatsby', @@ -154,9 +373,11 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://www.gatsbyjs.com/docs', + seoDescription: + 'Add Pulse analytics to your Gatsby site using gatsby-ssr or the Gatsby Head API.', + relatedIds: ['react', 'nextjs', 'hugo'], }, - - // * ── Static-Site Generators ──────────────────────────────────────────────── { id: 'hugo', name: 'Hugo', @@ -168,9 +389,110 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://gohugo.io/documentation', + seoDescription: + 'Add Pulse analytics to your Hugo site via a partial or base template.', + relatedIds: ['jekyll', 'eleventy', 'astro'], + }, + { + id: 'eleventy', + name: 'Eleventy', + description: 'Add Pulse analytics to your Eleventy (11ty) static site.', + category: 'ssg', + brandColor: '#222222', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://www.11ty.dev/docs', + seoDescription: + 'Add Pulse analytics to your Eleventy (11ty) site. Template-based integration.', + relatedIds: ['hugo', 'jekyll', 'astro'], + }, + { + id: 'jekyll', + name: 'Jekyll', + description: 'Add Pulse analytics to your Jekyll site via Liquid templates.', + category: 'ssg', + brandColor: '#CC0000', + icon: ( + + + + ), + officialUrl: 'https://jekyllrb.com/docs', + seoDescription: + 'Add Pulse analytics to your Jekyll site. Liquid template integration for GitHub Pages and beyond.', + relatedIds: ['hugo', 'eleventy', 'github-pages'], + }, + { + id: 'docusaurus', + name: 'Docusaurus', + description: 'Add Pulse analytics to your Docusaurus documentation site.', + category: 'ssg', + brandColor: '#3ECC5F', + icon: ( + + + + ), + officialUrl: 'https://docusaurus.io/docs', + seoDescription: + 'Add Pulse analytics to your Docusaurus documentation site via docusaurus.config.js.', + relatedIds: ['vitepress', 'mkdocs', 'gatsby'], + }, + { + id: 'vitepress', + name: 'VitePress', + description: 'Add Pulse analytics to your VitePress documentation site.', + category: 'ssg', + brandColor: '#5C73E7', + icon: ( + + + + ), + officialUrl: 'https://vitepress.dev', + seoDescription: + 'Add Pulse analytics to your VitePress documentation site via config.', + relatedIds: ['docusaurus', 'vue', 'nuxt'], + }, + { + id: 'hexo', + name: 'Hexo', + description: 'Add Pulse analytics to your Hexo blog or documentation site.', + category: 'ssg', + brandColor: '#0E83CD', + icon: ( + + + + ), + officialUrl: 'https://hexo.io/docs', + seoDescription: + 'Add Pulse analytics to your Hexo blog or documentation site.', + relatedIds: ['hugo', 'jekyll', 'eleventy'], + }, + { + id: 'mkdocs', + name: 'MkDocs', + description: 'Add Pulse analytics to your MkDocs documentation site.', + category: 'ssg', + brandColor: '#526CFE', + icon: ( + + + + ), + officialUrl: 'https://www.mkdocs.org', + seoDescription: + 'Add Pulse analytics to your MkDocs documentation site.', + relatedIds: ['docusaurus', 'vitepress', 'django'], }, - // * ── CMS & Blogging ─────────────────────────────────────────────────────── + // * ─── CMS & Blogging ────────────────────────────────────────────────────── { id: 'wordpress', name: 'WordPress', @@ -182,6 +504,10 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://wordpress.org/documentation', + seoDescription: + 'Add Pulse analytics to your WordPress site via a plugin or theme header code.', + relatedIds: ['ghost', 'drupal', 'woocommerce'], }, { id: 'ghost', @@ -195,21 +521,178 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://ghost.org/docs', + seoDescription: + 'Add Pulse analytics to your Ghost blog via Code Injection settings.', + relatedIds: ['wordpress', 'blogger'], + }, + { + id: 'drupal', + name: 'Drupal', + description: 'Add Pulse analytics to your Drupal site using a module or theme.', + category: 'cms', + brandColor: '#0678BE', + icon: ( + + + + ), + officialUrl: 'https://www.drupal.org/docs', + seoDescription: + 'Add Pulse analytics to your Drupal site using a module or theme template.', + relatedIds: ['wordpress', 'joomla'], + }, + { + id: 'joomla', + name: 'Joomla', + description: 'Add Pulse analytics to your Joomla site via template or extension.', + category: 'cms', + brandColor: '#5091CD', + icon: ( + + + + ), + officialUrl: 'https://docs.joomla.org', + seoDescription: + 'Add Pulse analytics to your Joomla site via template or extension.', + relatedIds: ['wordpress', 'drupal'], + }, + { + id: 'strapi', + name: 'Strapi', + description: 'Add Pulse analytics to your Strapi-powered frontend.', + category: 'cms', + brandColor: '#4945FF', + icon: ( + + + + ), + officialUrl: 'https://docs.strapi.io', + seoDescription: + 'Add Pulse analytics to your Strapi-powered frontend.', + relatedIds: ['contentful', 'sanity', 'nextjs'], + }, + { + id: 'sanity', + name: 'Sanity', + description: 'Add Pulse analytics to your Sanity-powered frontend.', + category: 'cms', + brandColor: '#0D0E12', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://www.sanity.io/docs', + seoDescription: + 'Add Pulse analytics to your Sanity-powered frontend application.', + relatedIds: ['strapi', 'contentful', 'nextjs'], + }, + { + id: 'contentful', + name: 'Contentful', + description: 'Add Pulse analytics to your Contentful-powered frontend.', + category: 'cms', + brandColor: '#2478CC', + icon: ( + + + + ), + officialUrl: 'https://www.contentful.com/developers/docs', + seoDescription: + 'Add Pulse analytics to your Contentful-powered frontend.', + relatedIds: ['strapi', 'sanity', 'nextjs'], + }, + { + id: 'payload', + name: 'Payload CMS', + description: 'Add Pulse analytics to your Payload CMS frontend.', + category: 'cms', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://payloadcms.com/docs', + seoDescription: + 'Add Pulse analytics to your Payload CMS frontend application.', + relatedIds: ['strapi', 'contentful', 'nextjs'], }, - // * ── Platforms & Builders ────────────────────────────────────────────────── + // * ─── eCommerce ──────────────────────────────────────────────────────────── { id: 'shopify', name: 'Shopify', description: 'Add privacy-first analytics to your Shopify store via theme editor.', - category: 'platform', + category: 'ecommerce', brandColor: '#7AB55C', icon: ( ), + officialUrl: 'https://shopify.dev/docs', + seoDescription: + 'Add Pulse privacy-first analytics to your Shopify store via the theme editor.', + relatedIds: ['woocommerce', 'bigcommerce', 'prestashop'], }, + { + id: 'woocommerce', + name: 'WooCommerce', + description: 'Add Pulse analytics to your WooCommerce store.', + category: 'ecommerce', + brandColor: '#96588A', + icon: ( + + + + ), + officialUrl: 'https://woocommerce.com/documentation', + seoDescription: + 'Add Pulse analytics to your WooCommerce store. WordPress-based setup.', + relatedIds: ['shopify', 'wordpress', 'bigcommerce'], + }, + { + id: 'bigcommerce', + name: 'BigCommerce', + description: 'Add Pulse analytics to your BigCommerce store.', + category: 'ecommerce', + brandColor: '#121118', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://developer.bigcommerce.com/docs', + seoDescription: + 'Add Pulse analytics to your BigCommerce store via Script Manager.', + relatedIds: ['shopify', 'woocommerce', 'prestashop'], + }, + { + id: 'prestashop', + name: 'PrestaShop', + description: 'Add Pulse analytics to your PrestaShop store.', + category: 'ecommerce', + brandColor: '#DF0067', + icon: ( + + + + ), + officialUrl: 'https://devdocs.prestashop-project.org', + seoDescription: + 'Add Pulse analytics to your PrestaShop store via theme template.', + relatedIds: ['shopify', 'woocommerce', 'bigcommerce'], + }, + + // * ─── Platforms & Tools ──────────────────────────────────────────────────── { id: 'webflow', name: 'Webflow', @@ -221,6 +704,10 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://university.webflow.com', + seoDescription: + 'Add Pulse analytics to your Webflow site via project custom code settings.', + relatedIds: ['squarespace', 'wix', 'framer'], }, { id: 'squarespace', @@ -234,6 +721,10 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://support.squarespace.com', + seoDescription: + 'Add Pulse analytics to your Squarespace site via the Code Injection panel.', + relatedIds: ['webflow', 'wix', 'carrd'], }, { id: 'wix', @@ -246,6 +737,159 @@ export const integrations: Integration[] = [ ), + officialUrl: 'https://support.wix.com', + seoDescription: + 'Add Pulse analytics to your Wix site via Custom Code settings.', + relatedIds: ['webflow', 'squarespace', 'framer'], + }, + { + id: 'framer', + name: 'Framer', + description: 'Add Pulse analytics to your Framer site via custom code.', + category: 'platform', + brandColor: '#0055FF', + icon: ( + + + + ), + officialUrl: 'https://www.framer.com/help', + seoDescription: + 'Add Pulse analytics to your Framer site via custom code settings.', + relatedIds: ['webflow', 'squarespace', 'wix'], + }, + { + id: 'carrd', + name: 'Carrd', + description: 'Add Pulse analytics to your Carrd one-page site.', + category: 'platform', + brandColor: '#596CAF', + icon: ( + + + + ), + officialUrl: 'https://carrd.co/docs', + seoDescription: + 'Add Pulse analytics to your Carrd one-page site via head code.', + relatedIds: ['framer', 'webflow'], + }, + { + id: 'blogger', + name: 'Blogger', + description: 'Add Pulse analytics to your Blogger blog.', + category: 'platform', + brandColor: '#FF5722', + icon: ( + + + + ), + officialUrl: 'https://support.google.com/blogger', + seoDescription: + 'Add Pulse analytics to your Blogger blog via theme HTML editor.', + relatedIds: ['wordpress', 'ghost'], + }, + { + id: 'gtm', + name: 'Google Tag Manager', + description: 'Add Pulse analytics via Google Tag Manager.', + category: 'platform', + brandColor: '#246FDB', + icon: ( + + + + ), + officialUrl: 'https://tagmanager.google.com', + seoDescription: + 'Add Pulse analytics via Google Tag Manager. Works with any site using GTM.', + relatedIds: ['wordpress', 'shopify', 'webflow'], + }, + { + id: 'notion', + name: 'Notion', + description: 'Add Pulse analytics to Notion-powered websites.', + category: 'platform', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://www.notion.so', + seoDescription: + 'Add Pulse analytics to Notion-powered websites using Super.so, Potion, or similar tools.', + relatedIds: ['webflow', 'framer', 'carrd'], + }, + + // * ─── Hosting & Deployment ───────────────────────────────────────────────── + { + id: 'cloudflare-pages', + name: 'Cloudflare Pages', + description: 'Deploy with Pulse analytics on Cloudflare Pages.', + category: 'hosting', + brandColor: '#F38020', + icon: ( + + + + ), + officialUrl: 'https://developers.cloudflare.com/pages', + seoDescription: + 'Deploy with Pulse analytics on Cloudflare Pages. Works with any framework.', + relatedIds: ['netlify', 'vercel', 'github-pages'], + }, + { + id: 'netlify', + name: 'Netlify', + description: 'Add Pulse analytics to sites deployed on Netlify.', + category: 'hosting', + brandColor: '#00C7B7', + icon: ( + + + + ), + officialUrl: 'https://docs.netlify.com', + seoDescription: + 'Add Pulse analytics to sites deployed on Netlify. Snippet injection and build-based methods.', + relatedIds: ['cloudflare-pages', 'vercel', 'github-pages'], + }, + { + id: 'vercel', + name: 'Vercel', + description: 'Add Pulse analytics to sites deployed on Vercel.', + category: 'hosting', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://vercel.com/docs', + seoDescription: + 'Add Pulse analytics to sites deployed on Vercel. Works with any framework.', + relatedIds: ['netlify', 'cloudflare-pages', 'nextjs'], + }, + { + id: 'github-pages', + name: 'GitHub Pages', + description: 'Add Pulse analytics to your GitHub Pages site.', + category: 'hosting', + brandColor: '#222222', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://docs.github.com/en/pages', + seoDescription: + 'Add Pulse analytics to your GitHub Pages site. Works with Jekyll, Hugo, or plain HTML.', + relatedIds: ['jekyll', 'hugo', 'netlify'], }, ] @@ -257,7 +901,11 @@ export function getIntegration(id: string): Integration | undefined { } /** Group integrations by category, preserving category ordering. */ -export function getGroupedIntegrations(): { category: IntegrationCategory; label: string; items: Integration[] }[] { +export function getGroupedIntegrations(): { + category: IntegrationCategory + label: string + items: Integration[] +}[] { return categoryOrder .map((cat) => ({ category: cat, From fe6a0b9ebddcf93fbdaa7efc09d4ff1b65100d9c Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sat, 7 Feb 2026 01:14:02 +0100 Subject: [PATCH 3/4] feat: update integration guides and add new CMS integrations for Craft CMS, Statamic, TYPO3, Kirby, Grav, and Umbraco --- lib/integration-guides.tsx | 960 ++++++++++++++++++++++++++++++++++++- lib/integrations.tsx | 423 +++++++++++++++- 2 files changed, 1381 insertions(+), 2 deletions(-) diff --git a/lib/integration-guides.tsx b/lib/integration-guides.tsx index 94b11be..43dba84 100644 --- a/lib/integration-guides.tsx +++ b/lib/integration-guides.tsx @@ -4,7 +4,7 @@ * Each guide is keyed by the integration slug and rendered on the * `/integrations/[slug]` page. * - * * 50 guides across 7 categories. + * * 75 guides across 7 categories. */ import { type ReactNode } from 'react' @@ -2623,6 +2623,964 @@ export default defineConfig({

), + + /* ──────────────────────────────────────────────────────────────────────────── + * 51. Craft CMS + * ──────────────────────────────────────────────────────────────────────────── */ + 'craftcms': ( + <> +

+ Add Pulse to your Craft CMS site by editing your Twig layout template. +

+ +
+ +

Add the Pulse Script to Your Layout

+

+ Edit your main Twig layout template at{' '} + templates/_layout.twig and add the Pulse script inside the{' '} + <head> section. +

+ {` + + + + + + + + {{ siteName }} + + + {% block content %}{% endblock %} + +`} + +

+ For more details, see the{' '} + + Craft CMS docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 52. Statamic + * ──────────────────────────────────────────────────────────────────────────── */ + 'statamic': ( + <> +

+ Statamic is a Laravel-based CMS. Add Pulse to your Antlers or Blade + layout. +

+ +
+ +

Add the Pulse Script to Your Layout

+

+ Edit your Antlers layout at{' '} + resources/views/layout.antlers.html and add the Pulse + script inside the <head> section. +

+ {` + + + + + + + + {{ title }} + + + {{ template_content }} + +`} + +

+ For more details, see the{' '} + + Statamic views docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 53. TYPO3 + * ──────────────────────────────────────────────────────────────────────────── */ + 'typo3': ( + <> +

+ Add Pulse to your TYPO3 site via TypoScript setup. +

+ +
+ +

Add the Pulse Script via TypoScript

+

+ Add the following to your setup.typoscript file to inject + the Pulse script into the page header. +

+ {`page.headerData.999 = TEXT +page.headerData.999.value = `} + +

+ For more details, see the{' '} + + TypoScript reference + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 54. Kirby + * ──────────────────────────────────────────────────────────────────────────── */ + 'kirby': ( + <> +

+ Add Pulse to your Kirby site via a PHP snippet. +

+ +
+ +

Add the Pulse Script to Your Header Snippet

+

+ Edit site/snippets/header.php or{' '} + site/templates/default.php and add the Pulse script before + the closing </head> tag. +

+ {` + + + + + + + + <?= $page->title() ?> +`} + +

+ For more details, see the{' '} + + Kirby snippets docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 55. Grav + * ──────────────────────────────────────────────────────────────────────────── */ + 'grav': ( + <> +

+ Add Pulse to your Grav site via Twig templates. +

+ +
+ +

Add the Pulse Script to Your Base Template

+

+ Edit your theme's base template at{' '} + templates/partials/base.html.twig and add the Pulse script + inside the head block. +

+ {` + + + {% block head %} + + + + + + {{ page.title }} + {% endblock %} + + + {% block content %}{% endblock %} + +`} + +

+ For more details, see the{' '} + + Grav Twig docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 56. Umbraco + * ──────────────────────────────────────────────────────────────────────────── */ + 'umbraco': ( + <> +

+ Add Pulse to your Umbraco site via a Razor layout view. +

+ +
+ +

Add the Pulse Script to Your Layout

+

+ Edit Views/Shared/_Layout.cshtml and add the Pulse script + before the closing </head> tag. Use an environment + tag guard to only load in production. +

+ {` + + + + + + + + + + @ViewData["Title"] + + + @RenderBody() + +`} + +

+ For more details, see the{' '} + + Umbraco templates docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 57. Storyblok + * ──────────────────────────────────────────────────────────────────────────── */ + 'storyblok': ( + <> +

+ Storyblok is a headless CMS — add Pulse to the frontend that renders + your Storyblok content. +

+ +
+ +

Add Pulse to Your Frontend

+

+ Since Storyblok is a headless CMS, the analytics script goes in your + frontend framework. Follow the guide for your framework: +

+ + +

+ For more details, see the{' '} + + Storyblok docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 58. Prismic + * ──────────────────────────────────────────────────────────────────────────── */ + 'prismic': ( + <> +

+ Prismic is a headless CMS — add Pulse to the frontend that displays + your Prismic content. +

+ +
+ +

Add Pulse to Your Frontend

+

+ Since Prismic is a headless CMS, the analytics script goes in your + frontend framework. Follow the guide for your framework: +

+ + +

+ For more details, see the{' '} + + Prismic docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 59. Shopware + * ──────────────────────────────────────────────────────────────────────────── */ + 'shopware': ( + <> +

+ Add Pulse to your Shopware 6 store via theme template. +

+ +
+ +

Add the Pulse Script to Your Theme

+

+ Edit{' '} + Resources/views/storefront/layout/meta.html.twig in your + theme and add the Pulse script in the base_header block. +

+ {`{% sw_extends '@Storefront/storefront/layout/meta.html.twig' %} + +{% block base_header %} + {{ parent() }} + + +{% endblock %}`} + +

+ For more details, see the{' '} + + Shopware themes docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 60. Magento / Adobe Commerce + * ──────────────────────────────────────────────────────────────────────────── */ + 'magento': ( + <> +

+ Add Pulse to your Magento / Adobe Commerce store via layout XML. +

+ +
+ +

Method 1: Layout XML

+

+ Add the Pulse script to your theme's layout XML at{' '} + app/design/frontend/YOUR_THEME/default/Magento_Theme/layout/default_head_blocks.xml. +

+ {` + + `} + +

+ For more details, see the{' '} + + Magento layout docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 61. Bubble + * ──────────────────────────────────────────────────────────────────────────── */ + 'bubble': ( + <> +

+ Add Pulse to your Bubble app via the SEO / Meta tags section. +

+ +
+ +

Add the Pulse Script via Settings

+

+ Go to Settings → SEO / Metatags → Script/meta tags in + header and paste the Pulse script. +

+ {``} + +

+ For more details, see the{' '} + + Bubble settings docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 62. Discourse + * ──────────────────────────────────────────────────────────────────────────── */ + 'discourse': ( + <> +

+ Add Pulse to your Discourse forum via admin customization. +

+ +
+ +

Add the Pulse Script via Theme Customization

+

+ Go to Admin → Customize → Themes → Edit + CSS/HTML and add the Pulse script in the{' '} + </head> section. +

+ {``} + +

+ For more details, see the{' '} + + Discourse themes guide + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 63. HubSpot + * ──────────────────────────────────────────────────────────────────────────── */ + 'hubspot': ( + <> +

+ Add Pulse to your HubSpot-hosted pages via site settings. +

+ +
+ +

Add the Pulse Script via Site Settings

+

+ Go to Settings → Website → Pages → Site Header + HTML and paste the Pulse script. +

+ {``} + +

+ Works for HubSpot CMS Free, Starter, Pro, and Enterprise. +

+ +

+ For more details, see the{' '} + + HubSpot header code docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 64. Substack + * ──────────────────────────────────────────────────────────────────────────── */ + 'substack': ( + <> +

+ Substack supports custom domains. Add Pulse tracking for your custom + domain. +

+ +
+ +

Track Your Substack via a Custom Domain

+

+ Substack doesn't allow custom scripts directly. You can track your + Substack via your custom domain by configuring Pulse to track the custom + domain. +

+
    +
  1. Set up your custom domain in Substack.
  2. +
  3. Add your custom domain in the Pulse dashboard.
  4. +
  5. Pulse will automatically track page views on your custom domain.
  6. +
+ +

+ For more details, see the{' '} + + Substack custom domain docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 65. Linktree + * ──────────────────────────────────────────────────────────────────────────── */ + 'linktree': ( + <> +

+ Add Pulse to your Linktree page via custom code (Business plan + required). +

+ +
+ +

Add the Pulse Script via Custom Meta Tags

+

+ Go to Settings → SEO → Custom Meta Tags and + add the Pulse script to the head. +

+ {``} + +

+ Note: Requires Linktree Business or Enterprise plan. +

+ +

+ For more details, see{' '} + + Linktree Business + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 66. Weebly + * ──────────────────────────────────────────────────────────────────────────── */ + 'weebly': ( + <> +

+ Add Pulse to your Weebly site via the header code settings. +

+ +
+ +

Add the Pulse Script via SEO Settings

+

+ Go to Settings → SEO → Header Code and paste + the Pulse script. +

+ {``} + +

+ For more details, see the{' '} + + Weebly SEO docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 67. GitBook + * ──────────────────────────────────────────────────────────────────────────── */ + 'gitbook': ( + <> +

+ Add Pulse to your GitBook documentation site. +

+ +
+ +

Add the Pulse Script via Custom Scripts

+

+ GitBook supports custom header integrations. Go to{' '} + Space settings → Integrations → Custom + scripts and add the Pulse script. +

+ {``} + +

+ For more details, see the{' '} + + GitBook customization docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 68. Gridsome + * ──────────────────────────────────────────────────────────────────────────── */ + 'gridsome': ( + <> +

+ Add Pulse to your Gridsome site via the HTML template or a plugin. +

+ +
+ +

Method 1: HTML Template

+

+ Edit src/index.html and add the Pulse script to the head + section. +

+ {` + + + \${head} + + + + + \${app} + +`} + +

Method 2: Server Configuration

+

+ Use gridsome.server.js to inject the script + programmatically. +

+ {`module.exports = function (api) { + api.afterBuild(({ queue }) => { + // Script injection logic + }) +}`} + +

+ For more details, see the{' '} + + Gridsome head management docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 69. Read the Docs + * ──────────────────────────────────────────────────────────────────────────── */ + 'readthedocs': ( + <> +

+ Add Pulse to your Read the Docs documentation. +

+ +
+ +

Add the Pulse Script via Sphinx Configuration

+

+ Create a custom template override. In your Sphinx{' '} + conf.py, add the Pulse script as a JavaScript file. +

+ {`html_js_files = [ + ('https://pulse.ciphera.net/script.js', {'defer': 'defer', 'data-domain': 'your-site.com'}), +]`} + +

+ For more details, see the{' '} + + Read the Docs customization + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 70. Sphinx + * ──────────────────────────────────────────────────────────────────────────── */ + 'sphinx': ( + <> +

+ Add Pulse to your Sphinx documentation via conf.py. +

+ +
+ +

Add the Pulse Script via Configuration

+

+ In your conf.py, add the Pulse script using{' '} + html_js_files. +

+ {`html_js_files = [ + ('https://pulse.ciphera.net/script.js', {'defer': 'defer', 'data-domain': 'your-site.com'}), +]`} + +

+ For more details, see the{' '} + + Sphinx html_js_files docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 71. ReadMe + * ──────────────────────────────────────────────────────────────────────────── */ + 'readme': ( + <> +

+ Add Pulse to your ReadMe API documentation portal. +

+ +
+ +

Add the Pulse Script via Custom JavaScript

+

+ Go to Project Settings → Custom JavaScript and + paste the Pulse script. +

+ {``} + +

+ For more details, see the{' '} + + ReadMe custom JavaScript docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 72. Flutter Web + * ──────────────────────────────────────────────────────────────────────────── */ + 'flutter': ( + <> +

+ Add Pulse to your Flutter web application via{' '} + web/index.html. +

+ +
+ +

Add the Pulse Script to Your HTML Template

+

+ Edit web/index.html in your Flutter project and add the + Pulse script to the <head> section. +

+ {` + + + + + + + + My Flutter App + + + + +`} + +

+ Note: This only applies to Flutter Web. For Flutter + mobile apps, Pulse tracks web views only. +

+ +

+ For more details, see the{' '} + + Flutter Web docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 73. Render + * ──────────────────────────────────────────────────────────────────────────── */ + 'render': ( + <> +

+ Add Pulse via your framework setup. Render deploys framework projects. +

+ +
+ +

Follow Your Framework's Guide

+

+ The analytics script goes in your source code, not in Render's + dashboard. Follow your framework's integration guide: +

+ + +

+ For more details, see the{' '} + + Render deployment docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 74. Firebase Hosting + * ──────────────────────────────────────────────────────────────────────────── */ + 'firebase': ( + <> +

+ Add Pulse to sites deployed on Firebase Hosting. +

+ +
+ +

Add the Pulse Script to Your Source Code

+

+ Follow your framework's integration guide. The analytics script + goes in your source code. Firebase Hosting serves static files, so add + the script to your index.html or framework layout. +

+ {` + + + + + + + + My App + + +
+ +`}
+ +

+ For more details, see the{' '} + + Firebase Hosting docs + . +

+ + ), + + /* ──────────────────────────────────────────────────────────────────────────── + * 75. AMP + * ──────────────────────────────────────────────────────────────────────────── */ + 'amp': ( + <> +

+ Add Pulse to your AMP pages using the amp-analytics{' '} + component. +

+ +
+ +

Add Pulse via amp-analytics

+

+ AMP pages have restrictions on JavaScript. The{' '} + amp-analytics component is the standard way to add + analytics. Add the following to your AMP page. +

+ {` + +`} + +

+ Note: AMP pages have restrictions on JavaScript. The{' '} + amp-analytics component is the standard way to add + analytics. +

+ +

+ For more details, see the{' '} + + amp-analytics docs + . +

+ + ), } // * ─── Public API ───────────────────────────────────────────────────────────── diff --git a/lib/integrations.tsx b/lib/integrations.tsx index 457c3dd..0c6c8fc 100644 --- a/lib/integrations.tsx +++ b/lib/integrations.tsx @@ -5,7 +5,7 @@ * ! SVG paths sourced from simple-icons (https://simpleicons.org). * ! All icons use a 24×24 viewBox. * - * * 50 integrations across 7 categories. + * * 75 integrations across 7 categories. */ import { type ReactNode } from 'react' @@ -891,6 +891,427 @@ export const integrations: Integration[] = [ 'Add Pulse analytics to your GitHub Pages site. Works with Jekyll, Hugo, or plain HTML.', relatedIds: ['jekyll', 'hugo', 'netlify'], }, + + // * ─── CMS & Blogging (continued) ────────────────────────────────────────── + { + id: 'craftcms', + name: 'Craft CMS', + description: 'Add Pulse to your Craft CMS site with a simple template tag.', + category: 'cms', + brandColor: '#E5422B', + icon: ( + + + + ), + officialUrl: 'https://craftcms.com/docs', + seoDescription: + 'Add Pulse analytics to your Craft CMS site via Twig templates. Simple installation.', + relatedIds: ['wordpress', 'statamic', 'drupal'], + }, + { + id: 'statamic', + name: 'Statamic', + description: 'Integrate Pulse with your Statamic site via Antlers templates.', + category: 'cms', + brandColor: '#FF269E', + icon: ( + + + + ), + officialUrl: 'https://statamic.dev/docs', + seoDescription: + 'Add Pulse analytics to your Statamic site. Antlers template integration.', + relatedIds: ['craftcms', 'laravel', 'wordpress'], + }, + { + id: 'typo3', + name: 'TYPO3', + description: 'Add Pulse to your TYPO3 site via TypoScript or Fluid templates.', + category: 'cms', + brandColor: '#FF8700', + icon: ( + + + + ), + officialUrl: 'https://docs.typo3.org', + seoDescription: + 'Add Pulse analytics to your TYPO3 CMS site. TypoScript and Fluid template integration.', + relatedIds: ['wordpress', 'drupal', 'joomla'], + }, + { + id: 'kirby', + name: 'Kirby', + description: 'Add Pulse to your Kirby site via PHP snippets.', + category: 'cms', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://getkirby.com/docs', + seoDescription: + 'Add Pulse analytics to your Kirby CMS site. Simple PHP snippet integration.', + relatedIds: ['craftcms', 'statamic', 'grav'], + }, + { + id: 'grav', + name: 'Grav', + description: 'Add Pulse to your Grav site via Twig templates.', + category: 'cms', + brandColor: '#221E1F', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://learn.getgrav.org', + seoDescription: + 'Add Pulse analytics to your Grav flat-file CMS. Twig template integration.', + relatedIds: ['kirby', 'craftcms', 'hugo'], + }, + { + id: 'umbraco', + name: 'Umbraco', + description: 'Add Pulse to your Umbraco site via Razor views.', + category: 'cms', + brandColor: '#3544B1', + icon: ( + + + + ), + officialUrl: 'https://docs.umbraco.com', + seoDescription: + 'Add Pulse analytics to your Umbraco CMS site. Razor view integration for .NET.', + relatedIds: ['wordpress', 'drupal', 'typo3'], + }, + { + id: 'storyblok', + name: 'Storyblok', + description: 'Add Pulse to the frontend of your Storyblok-powered site.', + category: 'cms', + brandColor: '#09B3AF', + icon: ( + + + + ), + officialUrl: 'https://www.storyblok.com/docs', + seoDescription: + 'Add Pulse analytics to your Storyblok-powered frontend application.', + relatedIds: ['contentful', 'prismic', 'nextjs'], + }, + { + id: 'prismic', + name: 'Prismic', + description: 'Add Pulse to the frontend of your Prismic-powered site.', + category: 'cms', + brandColor: '#5163BA', + icon: ( + + + + ), + officialUrl: 'https://prismic.io/docs', + seoDescription: + 'Add Pulse analytics to your Prismic-powered frontend application.', + relatedIds: ['contentful', 'storyblok', 'nextjs'], + }, + + // * ─── eCommerce (continued) ─────────────────────────────────────────────── + { + id: 'shopware', + name: 'Shopware', + description: 'Add Pulse analytics to your Shopware 6 store.', + category: 'ecommerce', + brandColor: '#189EFF', + icon: ( + + + + ), + officialUrl: 'https://developer.shopware.com/docs', + seoDescription: + 'Add Pulse analytics to your Shopware 6 store via theme template.', + relatedIds: ['shopify', 'woocommerce', 'magento'], + }, + { + id: 'magento', + name: 'Magento', + description: 'Add Pulse analytics to your Magento / Adobe Commerce store.', + category: 'ecommerce', + brandColor: '#F46F25', + icon: ( + + + + ), + officialUrl: 'https://developer.adobe.com/commerce', + seoDescription: + 'Add Pulse analytics to your Magento or Adobe Commerce store via layout XML.', + relatedIds: ['shopify', 'woocommerce', 'shopware'], + }, + + // * ─── Platforms & Tools (continued) ─────────────────────────────────────── + { + id: 'bubble', + name: 'Bubble', + description: 'Add Pulse to your Bubble no-code application.', + category: 'platform', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://manual.bubble.io', + seoDescription: + 'Add Pulse analytics to your Bubble no-code app via the SEO/Meta tags section.', + relatedIds: ['webflow', 'framer', 'wix'], + }, + { + id: 'discourse', + name: 'Discourse', + description: 'Add Pulse to your Discourse community forum.', + category: 'platform', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://meta.discourse.org/docs', + seoDescription: + 'Add Pulse analytics to your Discourse forum via admin customization.', + relatedIds: ['wordpress', 'ghost'], + }, + { + id: 'hubspot', + name: 'HubSpot', + description: 'Add Pulse to your HubSpot-hosted pages.', + category: 'platform', + brandColor: '#FF7A59', + icon: ( + + + + ), + officialUrl: 'https://knowledge.hubspot.com', + seoDescription: + 'Add Pulse analytics to HubSpot landing pages and website via Settings.', + relatedIds: ['wordpress', 'webflow'], + }, + { + id: 'substack', + name: 'Substack', + description: 'Add Pulse to your Substack publication with a custom domain.', + category: 'platform', + brandColor: '#FF6719', + icon: ( + + + + ), + officialUrl: 'https://substack.com', + seoDescription: + 'Add Pulse analytics to your Substack publication using custom domain settings.', + relatedIds: ['ghost', 'blogger', 'wordpress'], + }, + { + id: 'linktree', + name: 'Linktree', + description: 'Add Pulse to your Linktree page via custom code.', + category: 'platform', + brandColor: '#43E55E', + icon: ( + + + + ), + officialUrl: 'https://linktr.ee', + seoDescription: + 'Add Pulse analytics to your Linktree link-in-bio page.', + relatedIds: ['carrd', 'framer', 'webflow'], + }, + { + id: 'weebly', + name: 'Weebly', + description: 'Add Pulse to your Weebly site via header code.', + category: 'platform', + brandColor: '#2C5CC5', + icon: ( + + + + ), + officialUrl: 'https://www.weebly.com', + seoDescription: + 'Add Pulse analytics to your Weebly website via the header code settings.', + relatedIds: ['squarespace', 'wix', 'webflow'], + }, + + // * ─── Static Sites & Documentation (continued) ─────────────────────────── + { + id: 'gitbook', + name: 'GitBook', + description: 'Add Pulse to your GitBook documentation site.', + category: 'ssg', + brandColor: '#BBDDE5', + icon: ( + + + + ), + officialUrl: 'https://docs.gitbook.com', + seoDescription: + 'Add Pulse analytics to your GitBook-hosted documentation.', + relatedIds: ['docusaurus', 'readme', 'readthedocs'], + }, + { + id: 'gridsome', + name: 'Gridsome', + description: 'Add Pulse to your Gridsome static site.', + category: 'ssg', + brandColor: '#00A672', + icon: ( + + + + ), + officialUrl: 'https://gridsome.org/docs', + seoDescription: + 'Add Pulse analytics to your Gridsome Vue-based static site.', + relatedIds: ['gatsby', 'vue', 'nuxt'], + }, + { + id: 'readthedocs', + name: 'Read the Docs', + description: 'Add Pulse to your Read the Docs documentation.', + category: 'ssg', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://docs.readthedocs.io', + seoDescription: + 'Add Pulse analytics to your Read the Docs documentation site.', + relatedIds: ['sphinx', 'mkdocs', 'docusaurus'], + }, + { + id: 'sphinx', + name: 'Sphinx', + description: 'Add Pulse to your Sphinx documentation via conf.py.', + category: 'ssg', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://www.sphinx-doc.org', + seoDescription: + 'Add Pulse analytics to your Sphinx-generated documentation.', + relatedIds: ['readthedocs', 'mkdocs', 'docusaurus'], + }, + { + id: 'readme', + name: 'ReadMe', + description: 'Add Pulse to your ReadMe API documentation.', + category: 'ssg', + brandColor: '#018EF5', + icon: ( + + + + ), + officialUrl: 'https://docs.readme.com', + seoDescription: + 'Add Pulse analytics to your ReadMe API documentation portal.', + relatedIds: ['gitbook', 'docusaurus', 'readthedocs'], + }, + + // * ─── JavaScript Frameworks (continued) ─────────────────────────────────── + { + id: 'flutter', + name: 'Flutter', + description: 'Add Pulse to your Flutter web application.', + category: 'framework', + brandColor: '#02569B', + icon: ( + + + + ), + officialUrl: 'https://docs.flutter.dev', + seoDescription: + 'Add Pulse analytics to your Flutter web app via web/index.html.', + relatedIds: ['react', 'angular', 'preact'], + }, + + // * ─── Hosting & Deployment (continued) ──────────────────────────────────── + { + id: 'render', + name: 'Render', + description: 'Deploy with Pulse analytics on Render.', + category: 'hosting', + brandColor: '#000000', + invertInDark: true, + icon: ( + + + + ), + officialUrl: 'https://docs.render.com', + seoDescription: + 'Add Pulse analytics to sites deployed on Render. Works with any framework.', + relatedIds: ['netlify', 'vercel', 'cloudflare-pages'], + }, + { + id: 'firebase', + name: 'Firebase Hosting', + description: 'Deploy with Pulse analytics on Firebase Hosting.', + category: 'hosting', + brandColor: '#DD2C00', + icon: ( + + + + ), + officialUrl: 'https://firebase.google.com/docs/hosting', + seoDescription: + 'Add Pulse analytics to sites deployed on Firebase Hosting.', + relatedIds: ['netlify', 'vercel', 'render'], + }, + + // * ─── Platforms & Tools (continued) ─────────────────────────────────────── + { + id: 'amp', + name: 'Google AMP', + description: 'Add Pulse to your AMP (Accelerated Mobile Pages) site.', + category: 'platform', + brandColor: '#005AF0', + icon: ( + + + + ), + officialUrl: 'https://amp.dev/documentation', + seoDescription: + 'Add Pulse analytics to Google AMP pages using amp-analytics.', + relatedIds: ['gtm', 'wordpress', 'webflow'], + }, ] // * ─── Helpers ──────────────────────────────────────────────────────────────── From 815c1ccc6c0bd4a8944305c56abc8cc621c8611f Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sat, 7 Feb 2026 01:23:31 +0100 Subject: [PATCH 4/4] feat: enhance integrations overview with category filters, search improvements, and popular integrations display --- app/integrations/page.tsx | 194 +++++++++++++++++++++++++++++++++----- 1 file changed, 169 insertions(+), 25 deletions(-) diff --git a/app/integrations/page.tsx b/app/integrations/page.tsx index ae66c7e..cb7b8ed 100644 --- a/app/integrations/page.tsx +++ b/app/integrations/page.tsx @@ -1,13 +1,14 @@ 'use client' /** - * @file Integrations overview page with search, grouped by category. + * @file Integrations overview page with search, category filters, and grouped grid. * - * Displays all 50 integrations in a filterable grid. - * When the search query returns no results, a "Missing something?" card is shown. + * Displays all 75+ integrations in a filterable, searchable grid. + * Features: search with result count, category chips, popular section, + * keyboard shortcut (/ to focus search), and "Missing something?" card. */ -import { useState, useMemo } from 'react' +import { useState, useMemo, useRef, useEffect, useCallback } from 'react' import Link from 'next/link' import { motion, AnimatePresence } from 'framer-motion' import { ArrowRightIcon } from '@ciphera-net/ui' @@ -18,32 +19,75 @@ import { type IntegrationCategory, } from '@/lib/integrations' +// * IDs of popular integrations shown in the pinned "Popular" row +const POPULAR_IDS = [ + 'nextjs', 'react', 'wordpress', 'shopify', 'webflow', 'vue', 'astro', 'vercel', +] + export default function IntegrationsPage() { const [query, setQuery] = useState('') + const [activeCategory, setActiveCategory] = useState('all') + const searchRef = useRef(null) - // * Filter integrations by name, description, or category label - const filteredGroups = useMemo(() => { + // * Keyboard shortcut: "/" to focus search + useEffect(() => { + function handleKeyDown(e: KeyboardEvent) { + if ( + e.key === '/' && + !['INPUT', 'TEXTAREA', 'SELECT'].includes((e.target as HTMLElement).tagName) + ) { + e.preventDefault() + searchRef.current?.focus() + } + } + document.addEventListener('keydown', handleKeyDown) + return () => document.removeEventListener('keydown', handleKeyDown) + }, []) + + // * Filter integrations by search query + active category + const { filteredGroups, totalResults, popularIntegrations } = useMemo(() => { const q = query.toLowerCase().trim() + const isSearching = q.length > 0 + const isCategoryFiltered = activeCategory !== 'all' - const filtered = q - ? integrations.filter( - (i) => - i.name.toLowerCase().includes(q) || - i.description.toLowerCase().includes(q) || - categoryLabels[i.category].toLowerCase().includes(q), - ) - : integrations + let filtered = integrations - return categoryOrder + if (isSearching) { + filtered = filtered.filter( + (i) => + i.name.toLowerCase().includes(q) || + i.description.toLowerCase().includes(q) || + categoryLabels[i.category].toLowerCase().includes(q), + ) + } + + if (isCategoryFiltered) { + filtered = filtered.filter((i) => i.category === activeCategory) + } + + const groups = categoryOrder .map((cat) => ({ category: cat as IntegrationCategory, label: categoryLabels[cat], items: filtered.filter((i) => i.category === cat), })) .filter((g) => g.items.length > 0) - }, [query]) + + // * Only show popular row when not searching/filtering + const popular = + !isSearching && !isCategoryFiltered + ? POPULAR_IDS.map((id) => integrations.find((i) => i.id === id)).filter(Boolean) + : [] + + return { filteredGroups: groups, totalResults: filtered.length, popularIntegrations: popular } + }, [query, activeCategory]) const hasResults = filteredGroups.length > 0 + const isFiltering = query.length > 0 || activeCategory !== 'all' + + const handleCategoryClick = useCallback((cat: IntegrationCategory | 'all') => { + setActiveCategory(cat) + }, []) return (
@@ -62,16 +106,22 @@ export default function IntegrationsPage() { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }} - className="text-center mb-12" + className="text-center mb-10" > -

- Integrations -

+ {/* * --- Title with count badge --- */} +
+

+ Integrations +

+ + {integrations.length}+ + +

- Connect Pulse with your favorite frameworks and platforms in minutes. + Connect Pulse with {integrations.length}+ frameworks and platforms in minutes.

- {/* * --- Search Input --- */} + {/* * --- Search Input with "/" hint --- */}
setQuery(e.target.value)} placeholder="Search integrations..." - className="w-full pl-12 pr-10 py-3 bg-white/70 dark:bg-neutral-900/70 backdrop-blur-sm border border-neutral-200 dark:border-neutral-800 rounded-xl text-neutral-900 dark:text-white placeholder:text-neutral-400 focus:outline-none focus:ring-2 focus:ring-brand-orange/50 focus:border-brand-orange/50 transition-all" + className="w-full pl-12 pr-16 py-3 bg-white/70 dark:bg-neutral-900/70 backdrop-blur-sm border border-neutral-200 dark:border-neutral-800 rounded-xl text-neutral-900 dark:text-white placeholder:text-neutral-400 focus:outline-none focus:ring-2 focus:ring-brand-orange/50 focus:border-brand-orange/50 transition-all" /> - {query && ( + {query ? ( + ) : ( +
+ + / + +
)}
+ + {/* * --- Result count (shown when filtering) --- */} + {isFiltering && ( + + {totalResults} {totalResults === 1 ? 'integration' : 'integrations'} found + {query && <> for “{query}”} + + )} + + + {/* * --- Category Filter Chips --- */} + + + {categoryOrder.map((cat) => ( + + ))} @@ -118,6 +219,49 @@ export default function IntegrationsPage() { exit={{ opacity: 0 }} transition={{ duration: 0.2 }} > + {/* * --- Popular Integrations (pinned row) --- */} + {popularIntegrations.length > 0 && ( +
+ + + + + Popular + + +
+ {popularIntegrations.map((integration, i) => ( + + +
+ {integration!.icon} +
+ + {integration!.name} + + +
+ ))} +
+
+ )} + + {/* * --- Category Groups --- */} {filteredGroups.map((group) => (