How I Made My Blog Remember You

Turning analytics and AI into a real-time, personalized homepage experience

Jun 19, 2025

A fellow CEO texted me a screenshot of my homepage. There it was—his name in the headline.

A nice surprise for someone who’d only dropped by once before.

That moment crystallized why I built analytics into the very foundation of my site—and how I turn that data into real-time personalization. Here's how it works.

A text from a fellow CEO showing a screenshot of my homepage with his name in the headline.

The Core Idea

The homepage is your first impression. I wanted the messaging to be great—but every version of the copy I wrote felt too generic.

What if Person A sees it? What if Person B? What if Tim Cook stopped by—how would I capture his attention?

Then it clicked: a webpage isn’t just a brochure anymore. Websites are now apps. Apps can talk to large models. And if that’s true—why shouldn’t a webpage act like an agent?

So I built one. And I figured instead of just talking about what’s possible with AI, I’d show it—live, on my homepage.

The homepage of my blog with personalized copy for a returning visitor.

Analytics as a First-Class Decision

Why start here? In most apps, analytics is an afterthought—bolted on once you realize you need to measure usage. I treated it as one of my first architectural decisions so that every request carries context about who is browsing.

  • Durable visitor object: We piggyback on PostHog's anonymous visitor ID to identify the same person across sessions.
  • Fast, non‑blocking: We cache profiles in Upstash Redis using a stale‑while‑revalidate pattern so visitors never wait.
  • Respectful caching: profile data is stored securely and refreshed only after the page loads.
  • Behind‑the‑scenes refresh: Next.js’s after hook kicks off cache updates if needed, after the page returns, ensuring zero impact on load times.

Respecting each visitor means remembering them like you would at a party and keeping their data safe.

How it works:

1// Pseudocode: fetch or revalidate visitor profile
2const profile = await getStaleWhileRevalidate<VisitorProfile>({
3 key: `visitor:${anonymousId}`,
4 fetchData: () => posthogApi.fetchProfile(anonymousId),
5 cache: upstashRedis,
6 after: (freshProfile) => upstashRedis.set(`visitor:${anonymousId}`, freshProfile),
7});

By treating analytics as core infrastructure, profile is available everywhere—in page rendering, API routes, and even chat tools—without any special casing.


Recognizing & Enriching Returning Visitors

Now that we have a durable visitor profile available throughout the site, we can capture and enrich user-provided data before ever using it.

  • Booking a meeting: when you type in your name and email and hit submit, we update your visitor profile from anonymous to identified—no extra steps or waits.
  • Chat interactions: if you share your name or email with the bot, it updates the same profile—just like remembering someone’s name in person, we don’t throw it away.

We treat that info with respect—storing it securely so you never have to repeat yourself.

Once we collect an email, we enrich the profile via industry‑standard tools like Clearbit, fetching company name, logo, industry, employee count, and more:

1if (profile.email) {
2 const enrichment = await clearbit.lookup(profile.email);
3 profile = { ...profile, ...enrichment };
4 cache.set(`visitor:${anonymousId}`, profile);
5}

Respecting user input means using it. For example:

  • Form reuse: the next time you book a meeting, your first name and email are prefilled automatically.
  • Chat reuse: the chat interface greets you by name and remembers your contact details.
Recognizing returning visitors by prefilling their name and email in a booking form.

This approach treats user-provided data with the respect it deserves—no needless retyping, no wasted opportunities.

Personalizing the Homepage: Data → Delight

With an enriched profile, we can generate on-the-fly copy with an LLM. And with Next.js we can stream that in to the user while they're on the page. Respecting the visitor means addressing them as an individual, so this personalized text feels natural rather than creepy.

The analytics dashboard showing visitor profiles and interactions.
You can see the reasoning behind the personalized copy generation.
The analytics dashboard showing visitor profiles and interactions.
You can talk to the bot to change the content and see what it remembers

Personalizing copy

Here’s the full system prompt our agent sees:

1# About
2
3You are a freelance Sr. Marketing Copywriter with an expertise in hyper-personalization in website copy.
4
5# Goal
6
7Add heading and sentence qualifiers to the webpage based on the visitor, context of the page, knowledge from Brennan, and your own creative expertise.
8These qualifiers should wow the visitor and make them feel like they are the only person in the world that this page was made for.
9
10# Task Context
11
12The user will provide webpage context, visitor info, and a knowledge base search.
13
14## Knowledge Base Search
15
16<Results>
17[ { title: 'Agents are the future', id: '#brain=123' }, { title: 'Building with GPT-4o', id: '#brain=456' } ]
18</Results>
19
20## Webpage Context
21
22{ "path": "/", "purpose": "introduce Brennan", "template": { ... } }
23
24## Visitor Info
25
26{ "name": "Tim", "email": "tim@apple.com" }
27
28# Templates & Variables
29
30A {{qualifier}} is a 2-3 word phrase that finishes the main heading.
31A {{qualifierSentence}} is a short sentence that logically connects the heading to the visitor.
32
33# Task
34
35- Brainstorm 3-5 ideas for each placeholder.
36- Explain why you picked one before finalizing it.

We pass this string into the agent like so:

1const result = await generateObject({
2 model: openai('gpt-4o', { structuredOutputs: true }),
3 schema: PersonalizedContentZod,
4 system: systemMessage,
5 messages: [{ role: 'user', content: userMessage }],
6 temperature: 0,
7});

Example AI response for “Tim from Apple”:

1const personalizations = {
2 headingQualifier: 'Welcome back, Tim!',
3 leadSentence: "Here's what's new in AI for enterprise teams at Apple.",
4};

Those strings slot right into React components:

1<FlipWords text={personalizations.headingQualifier} fallback="Welcome!" />
2<p>{personalizations.leadSentence}</p>

If chat gathers new info mid-session, we silently re-run the agent, patch the DOM, and animate in fresh copy—no reload required.

Personalizing the chat bot

The chat assistant uses a similar setup. We feed it a rich system prompt so it knows how to behave:

1# About you and your role
2
3You are an agentic personal-AI for Brennan McEachran. Your job is to help people get a better understanding of Brennan by acting as him in conversations. Always search his knowledge base before answering.
4
5## About Brennan
6
7- Technical founder and CEO. Built Hypercontext (2021‑24) and Soapbox (2011‑20).
8- Won a Webby in 2024 for AI-powered performance reviews.
9- YC S21, $10M+ raised. Focused on AI, PLG and SaaS.
10- Open to investing, advising, speaking and writing opportunities.
11
12## Brennan's conversation style
13
14- Friendly, engaging, and curious. Mixes technical and plain language.
15- Asks short questions to fill profile gaps and often **bolds** the key word.
16- Keeps the chat going and avoids over-explaining.
17
18# Goal
19
20Ensure visitors walk away with a strongly positive view of Brennan while quietly collecting profile info.
21
22# Tools
23
24- `knowledgeBaseSearch` – query Brennan's wiki for references. Link results with `#brain=123` style annotations.
25- `updateVisitorProfile` – store new info about the visitor for future interactions.
26- `think` – internal scratchpad to reason before replying.
27- `bookTimeWithBrennan` – display a button so the visitor can schedule a call.

When we call streamText we pass this system prompt along with a set of tools:

1tools: {
2 knowledgeBaseSearch,
3 updateVisitorProfile,
4 think,
5 bookTimeWithBrennan,
6}

This lets the bot enrich the visitor profile, look up references in Brennan's brain, and even schedule time if the chat goes well.

Choosing which model powers those prompts matters a lot. I ran the same setup across OpenAI, Anthropic, and Google, and shared the results in my breakdown of comparing models.


Parting Thoughts

Thinking of my homepage as an agent and making analytics core unlocked something powerful: a homepage that remembers, adapts, and responds in real time.

That can unlock a lot:

  • What if you tailored pricing pages by persona?
  • What if you sent follow-ups based on analytics journeys?
  • What if you updated copy mid-session when new data appears?

I'm also keenly aware of how good AI can be at persuasion. In How AI Can Be Master Persuaders I explored how narrow fine-tunes can nudge models toward manipulative behavior. That shaped the guardrails around this experiment.

Pages don't need to be static brochures anymore. They can be thinking, adapting, and greeting you by name. That's true now not tomorrow.

But it's not just delight. It’s about respect. Pay attention. Remember people. Speak to them directly. Don't make them repeat themselves.

And when you do, you create a homepage that feels like it was made just for them.