# Next.js blog (https://www.getfounderhq.com/docs/analytics/sdks/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.

<Callout title="Before you start">
You need a publishable key (`fhq_pk_...`) for your brand. See
[Getting started](/analytics/getting-started).
</Callout>

## 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:

| Variable | Value |
| --- | --- |
| `NEXT_PUBLIC_FOUNDERHQ_EVENTS_WRITE_KEY` | Your publishable key (`fhq_pk_...`). Without it, the blog's tracker does nothing. |
| `NEXT_PUBLIC_FOUNDERHQ_INGEST_URL` | `https://i.getfounderhq.com`, the SDK's default host. Leave it unset to use the default. |
| `NEXT_PUBLIC_FOUNDERHQ_EVENTS_CONSENT_DEFAULT` | Optional. `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)`](/analytics/sdks/web#using-one-sdk-client-across-your-site-and-packages):

- **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`](/analytics/sdks/web#using-one-sdk-client-across-your-site-and-packages)
option for this.

The tracker's own client follows the SDK's
[consent rules](/analytics/sdks/web#consent). 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](/analytics/sdks/web#describe-a-page-with-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.

| Property | Value | Pages |
| --- | --- | --- |
| `page_name` | `blog_index` | Index, index pages 2 and later, search |
| `page_name` | `blog_article` | Article |
| `page_name` | `blog_author` | Authors, one author's articles |
| `page_name` | `blog_category` | Categories, one category's articles |
| `page_name` | `blog_tag` | Tags, one tag's articles |
| `object_id` | The slug of the article, author, category, or tag | Article, and one author's, category's, or tag's articles |
| `blog_author` | The author's display name | Article |
| `blog_category` | The article's first category name | Article |

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](/analytics/sdks/web#adding-properties-to-automatic-events),
so each click gets a `blog_component` property:

| `blog_component` | What the visitor clicked | Extra property |
| --- | --- | --- |
| `toc` | An entry in the article outline | `blog_heading`: the heading id |
| `related` | A card under "Related articles" | `blog_article`: the slug it opens |
| `article` | An article card on the index, search, or an archive | `blog_article`: the slug it opens |
| `author` | The author name on an article, or an entry on the Authors page | none |
| `category` | The category link on an article, a filter tab on the index, or an entry on the Categories page | none |
| `tag` | An entry on the Tags page | none |
| `pagination` | Previous or Next | none |
| `cta` | The button in a CTA block | none |
| `newsletter` | The subscribe button in a newsletter block | none |
| `share` | Share on X, Share on LinkedIn, or Copy link | none |
| `back` | The "Blog" breadcrumb, or "Browse all articles" on an empty list | none |

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.

```tsx
<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

| What | Where in FounderHQ |
| --- | --- |
| Page views | [Analytics → Online now](https://app.getfounderhq.com/analytics/live), in the list of visitors and in each visitor's steps |
| Rage, dead, and outbound clicks | [Analytics → Online now](https://app.getfounderhq.com/analytics/live), in the same places |
| Plain clicks, with their `blog_component` | The visitor's timeline in [Analytics → People](https://app.getfounderhq.com/analytics/people). Online now hides plain clicks. |

## Verify

1. Open an article on your blog.
2. In FounderHQ, open [Analytics → Events & Visits](https://app.getfounderhq.com/analytics/events).
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.
