Internode content hub and landing page factory. The root of the site serves static, markdown-first answer pages on content.internode.ai, while campaign landing pages continue to live under /lp/[slug].
- Next.js 14 (App Router) + TypeScript + Tailwind CSS v4
- pnpm for installs
- Vercel for hosting (see
vercel.json) - PostHog for product analytics
-
Clone the repo and open the project root (
lp-factory). -
Install dependencies
pnpm install
-
Environment variables
cp .env.example .env.local
Edit
.env.localand set:NEXT_PUBLIC_POSTHOG_KEY— project API key from PostHogNEXT_PUBLIC_POSTHOG_HOST— usuallyhttps://us.i.posthog.com(or your self-hosted URL)
Do not commit
.env.local(it is gitignored). -
Run locally
pnpm dev
Open http://localhost:3000 to see the content hub homepage and the root-level content routes.
The public content surface is intentionally minimal:
- root homepage:
/ - root-level content pages:
/<slug> - LLM/search discovery files:
/robots.txt,/sitemap.xml,/rss.xml,/llms.txt,/llms-full.txt
- Store published pages in
content/answers/*.md - Use the starter templates in
content/templates/ - Keep slugs root-safe; avoid reserved paths such as
lp,robots.txt,sitemap.xml,rss.xml,llms.txt, andllms-full.txt
Each page is validated against a strict frontmatter schema in src/lib/content.ts.
Supported content types:
answeruse-caseupdate
Required frontmatter fields include:
titleslugdescriptionexcerpttypepublishedAtupdatedAtauthortagsctaHrefctaLabel
- Open
src/config/types.tsif you need new section shapes or fields. - Add a
CampaignConfigentry insrc/config/campaigns.ts(usecampaigns.set("your-slug", { ... })or extend theMapinitializer). - Include a unique
slug(URL path:/lp/your-slug),meta,theme,sections,cta, andtracking. - Commit and push to GitHub.
- Preview: push to any branch other than
main— GitHub Actions runs lint + build; Vercel creates a preview deployment if the project is connected. - Production: merge to
main— CI runs again; Vercel promotes production via the GitHub integration.
- Preview: push to any branch other than
Static params are generated from the campaign map, so new slugs are picked up on the next build.
| Goal | What to do |
|---|---|
| Preview URL | Push a branch (not main). CI validates the build; Vercel comments/links the preview when the Git integration is enabled. |
| Production | Merge to main. CI must pass; Vercel deploys production automatically. |
Project defaults in vercel.json:
- Region:
sfo1 - LP headers (paths matching
/lp/*):X-Robots-Tag: noindexso paid LPs do not compete with the content surface in organic search, plus short cache headers. Remove or adjustnoindexinvercel.jsonfor any LP you want indexed.
Set NEXT_PUBLIC_POSTHOG_* in the Vercel project Environment Variables for Preview and Production.
- Dashboard: PostHog (US cloud) — use your team/project URL after login.
- Useful events to monitor
$pageview— traffic and UTM-attributed landings (SPA route changes included).lp_click— CTA and tracked clicks (element,section,cta_text,campaign_slug, etc.).section_viewed— first time a section enters the viewport.section_time— cumulative visible time per section.scroll_depth— milestones (25 / 50 / 75 / 90 / 100%).- Conversion-related properties on clicks — e.g.
conversion_event(from campaign config) andcampaign_idin event properties.
Filter by campaign_slug, campaign_id, content_slug, or page_type to compare landing pages against the root content surface.
.github/workflows/preview.yml— on push to any branch exceptmain:pnpm install,pnpm lint,pnpm build..github/workflows/production.yml— on push tomain: same steps..github/workflows/indexnow-on-deploy.yml— fires whenever Vercel marks a Production deployment assuccess; POSTs the live sitemap toapi.indexnow.orgvia our signed endpoint.
Deploys are still performed by Vercel; these workflows catch broken builds before or alongside preview/production deploys.
The site auto-pings IndexNow (Bing, Yandex, and partner search engines) on every successful production deploy so freshly published or updated pages are re-crawled quickly.
- Vercel finishes a Production deploy and GitHub's Vercel integration marks the
deployment_statusassuccesswith environmentProduction. .github/workflows/indexnow-on-deploy.ymlreacts to that event andcurlsPOST https://content.internode.ai/api/indexnow-pingwith a Bearer token.src/pages/api/indexnow-ping.tsauthenticates the call, fetches/sitemap-index.xml, collects every<loc>, and submits the batch tohttps://api.indexnow.org/indexnowusing our key. The existing sitemap integration inastro.config.mjsfilters/lp/*out, so only indexable URLs are pushed.
Two values must match between Vercel (runtime) and GitHub (the workflow).
| Name | Where | Value | Purpose |
|---|---|---|---|
INDEXNOW_KEY |
Vercel → Project → Environment Variables (Production) | 001c1634ad984cb1852ab12b1fe36b74 |
Key IndexNow uses to authenticate submissions; must match public/001c1634ad984cb1852ab12b1fe36b74.txt so the keyLocation check passes. |
INDEXNOW_WEBHOOK_SECRET |
Vercel → Environment Variables (Production) and GitHub → Repo Settings → Secrets and variables → Actions | any long random string (e.g. openssl rand -hex 32) |
Shared secret the workflow sends as Authorization: Bearer … and the API route verifies. |
-
Key file must be reachable: https://content.internode.ai/001c1634ad984cb1852ab12b1fe36b74.txt should return exactly
001c1634ad984cb1852ab12b1fe36b74. -
Manual ping (smoke test):
curl -sS -X POST https://content.internode.ai/api/indexnow-ping \ -H "Authorization: Bearer $INDEXNOW_WEBHOOK_SECRET" \ -H "Content-Type: application/json"
A healthy response is
{"submitted":<n>,"status":"ok","indexNowStatus":200}. -
Workflow runs are visible under the repo's Actions tab as IndexNow on deploy; they are skipped for preview deploys by design.
If INDEXNOW_KEY ever needs to change, generate a new 32-char hex string, rename public/<newkey>.txt to match (with the new key as its only content), update the Vercel env var, and redeploy. IndexNow's own host-level "ownership" is proven by the file being reachable — no Bing Webmaster Tools reconfiguration is needed.
| Command | Description |
|---|---|
pnpm dev |
Development server |
pnpm build |
Production build |
pnpm start |
Start production server locally |
pnpm lint |
ESLint (Next.js config) |