Documentation
SDKs

Next.js blog

Track a blog built with @founderhq/next-blog.

A blog built with @founderhq/next-blog sends its page views and clicks to FounderHQ. Each page view says which blog page it was. Each click says which part of the blog the visitor used.

Before you start

You need a publishable key (fhq_pk_...) for your brand. See Getting started.

What it tracks

  • Page views with blog context. Every blog page view carries the page type and the slug. An article page view also carries its author and category.
  • Named clicks in the blog. A click on the outline, an article card, an author, a category, pagination, a CTA, the newsletter button, or a share button carries a name for that part of the blog.

Page views are sent only when a page opens. Clicks come from click capture, not from code in each button.

Turn it on

The installer (npx @founderhq/next-blog@latest init) writes these variables to .env.example. Set them in your environment:

VariableValue
NEXT_PUBLIC_FOUNDERHQ_EVENTS_WRITE_KEYYour publishable key (fhq_pk_...). Without it, the blog's tracker does nothing.
NEXT_PUBLIC_FOUNDERHQ_INGEST_URLhttps://i.getfounderhq.com, the SDK's default host. Leave it unset to use the default.
NEXT_PUBLIC_FOUNDERHQ_EVENTS_CONSENT_DEFAULTOptional. granted, pending, or denied: the consent state a visitor starts in. Leave it unset for granted.

An unset host uses the default. An empty string does not: the SDK sends to it as a host. The generated src/lib/founderhq-blog.ts passes NEXT_PUBLIC_FOUNDERHQ_INGEST_URL || undefined, so an empty value works like an unset one. If you build the events object yourself, do the same.

The generated blog layout renders <BlogEventsTracker> with these values. You do not add code.

How it uses your site's SDK client

Run one SDK client per write key on a page. Two clients with the same key count every page twice. So the blog reuses your site's client when there is one.

BlogEventsTracker waits until the page has loaded, or 1.5 seconds after the blog mounts if loading takes longer. Then it calls getFounderHqClient(writeKey):

  • Your site already runs @founderhq/events with the same key. The tracker adds nothing. Your client records the page views and clicks. A client in mode: "pageviews" records no clicks.
  • No client uses the key. The tracker starts its own client with autocapture: false. It records page views, and clicks inside the blog only. When the visitor leaves the blog, it sends what it has queued and stops.

The wait matters because a site often starts its SDK from code that loads with the page, or in a root component's effect, which React runs after the blog's. By the time the page has loaded, that client exists. The 1.5-second limit keeps a slow image or third-party script from costing the page views.

If your site's client starts even later, for example after a consent step, it takes over from the tracker's client: the tracker's client sends what it has queued and stops, and the visitor stays one person. From then on every page counts once. Only the page that is open at that moment can count twice. The tracker starts its client with the SDK's yield_to_site_client option for this.

The tracker's own client follows the SDK's consent rules. It reads a consent choice that your site's SDK remembered for the key. With no remembered choice, a visitor starts with consent granted, and the tracker records page views, clicks, and blog properties.

Your site shows a consent banner? Set NEXT_PUBLIC_FOUNDERHQ_EVENTS_CONSENT_DEFAULT=pending. The tracker then counts page views without cookies and records nothing else, until your site's SDK remembers the visitor's answer.

If your site runs the SDK itself from page load, you do not need the variable: the tracker finds your site's client and never starts its own.

Page properties

Each blog page renders <BlogPageProperties>. It adds fhq-prop meta tags, which the SDK adds to that page's page view.

It also adds <meta name="fhq-page-path"> with the path of the page it describes. The SDK reads the tags from <head>, and applies them only to a page view of that path. So a back or forward navigation never gets the properties of the page the visitor left.

On a slow navigation that shows a loading skeleton first, the SDK waits up to about 1 second for the new page's tags. After that, the page view goes out without the blog properties.

PropertyValuePages
page_nameblog_indexIndex, index pages 2 and later, search
page_nameblog_articleArticle
page_nameblog_authorAuthors, one author's articles
page_nameblog_categoryCategories, one category's articles
page_nameblog_tagTags, one tag's articles
object_idThe slug of the article, author, category, or tagArticle, and one author's, category's, or tag's articles
blog_authorThe author's display nameArticle
blog_categoryThe article's first category nameArticle

The values are your own public content names. They contain no data about the visitor.

Click names

The blog's <main> element carries data-fhq-autocapture. The SDK captures clicks inside it even when your client has autocapture: false. Links and buttons carry data-fhq-prop-* attributes, so each click gets a blog_component property:

blog_componentWhat the visitor clickedExtra property
tocAn entry in the article outlineblog_heading: the heading id
relatedA card under "Related articles"blog_article: the slug it opens
articleAn article card on the index, search, or an archiveblog_article: the slug it opens
authorThe author name on an article, or an entry on the Authors pagenone
categoryThe category link on an article, a filter tab on the index, or an entry on the Categories pagenone
tagAn entry on the Tags pagenone
paginationPrevious or Nextnone
ctaThe button in a CTA blocknone
newsletterThe subscribe button in a newsletter blocknone
shareShare on X, Share on LinkedIn, or Copy linknone
backThe "Blog" breadcrumb, or "Browse all articles" on an empty listnone

The SDK never records what the visitor types, so the newsletter email stays out of every event.

Add your own

The blog components are copied into your app (src/components/blog/blog-components.tsx). Edit them like any other component:

  • Name a click. Add data-fhq-prop-<key> to the element or a parent. The nearest value wins.
  • Describe a page. Render <meta name="fhq-prop-<key>" content="…" /> in the page component, next to <BlogPageProperties>, which gives the tags the page's path. Do not use generateMetadata: Next.js can stream it after the page view.
  • Keep a part out. Add data-fhq-no-capture to the element. No click inside it is captured.
<a href="/pricing" data-fhq-prop-blog-component="cta" data-fhq-prop-offer="trial">
  Start free
</a>

Keys use hyphens in markup and underscores in FounderHQ: data-fhq-prop-offer-code becomes offer_code.

Where you see it

WhatWhere in FounderHQ
Page viewsAnalytics → Online now, in the list of visitors and in each visitor's steps
Rage, dead, and outbound clicksAnalytics → Online now, in the same places
Plain clicks, with their blog_componentThe visitor's timeline in Analytics → People. Online now hides plain clicks.

Verify

  1. Open an article on your blog.
  2. In FounderHQ, open Analytics → Events & Visits.
  3. You see a $pageview with page_name: blog_article and the article's slug as object_id.
  4. Click a related article card.
  5. You see an $autocapture event with blog_component: related.

Troubleshoot

Every blog page counts twice

Two clients use the same key. If you host events.js yourself, replace your copy with the current one, so the tracker can find your site's client.

When your site's client starts after the tracker's, the page open at that moment can count twice. Later pages count once. This is expected.

Page views arrive with no blog properties, and no clicks arrive

The visitor declined consent on your site, and the blog's client honors that answer. It then counts page views without cookies and records nothing else.

AI agent or LLM? Read this page as markdown

On this page