Next.js (App Router), TypeScript, Tailwind CSS v4, and Payhip embedded checkout for the Dev Brain PDF. The hero shows the e-book cover from public/brand/dev-brain-e-book.png (via next/image), Sora for headings and Inter for body copy (loaded in app/globals.css). Optional Stripe API routes remain for session-based PDF download if you use that flow.
- Node.js 20.9+ (Next.js 16 and Tailwind CSS v4 expect Node 20+). Use a matching version when running
pnpm installso optional@tailwindcss/oxidenative binaries install correctly.
If you see Missing field negated on ScannerOptions.sources during next build (e.g. on Vercel), the project pins Tailwind 4.2.2+ to avoid a bad dependency split that shipped with early Tailwind v4.0.x.
Tailwind’s Rust-based scanner installs platform-specific optional packages. With pnpm, you need Node 20.9+ when you run pnpm install (otherwise those optional packages may not install). This repo uses public-hoist-pattern for Tailwind in .npmrc (not full node-linker=hoisted, which can break other tools) plus explicit optionalDependencies on the @tailwindcss/oxide-* binaries.
If you still see the error:
rm -rf node_modules
pnpm installConfirm node -v is v20.9 or newer before installing.
This project uses the App Router only (app/). If the browser mentions pages/_app or stale chunks, Turbopack is usually serving a cached client bundle.
rm -rf .next
pnpm devThen hard-reload the tab (or clear the site’s cache / unregister service workers for localhost).
Full node-linker=hoisted can break some packages’ postinstall paths. This repo uses targeted hoisting for Tailwind only (see .npmrc) and lists napi-postinstall as a devDependency so ESLint’s unrs-resolver postinstall can resolve correctly.
Copy .env.example to .env.local and fill in values.
| Variable | Description |
|---|---|
NEXT_PUBLIC_PAYHIP_PRODUCT_URL |
Required. The full Payhip product page link (must contain /b/PRODUCT_ID, e.g. https://payhip.com/b/AbCdE). Used to derive the direct checkout URL so buyers skip the product page and go straight to Payhip checkout. |
PAYHIP_WEBHOOK_SECRET |
Optional. Reserved for verifying Payhip webhook payloads if you add a webhook route later. |
Where to find the product URL: Payhip dashboard → Products → open your product → copy the product link (same URL you’d share with customers).
Buy links use Payhip’s /buy?link=PRODUCT_KEY pattern (built in lib/payhip.ts from your product URL). No Payhip JavaScript embed is required.
Used by app/api/checkout, /api/download, /api/webhooks/stripe, and related pages if you keep the Stripe session flow.
| Variable | Description |
|---|---|
NEXT_PUBLIC_APP_URL |
Site origin (no trailing slash), e.g. http://localhost:3000 |
STRIPE_SECRET_KEY |
Secret key from Stripe Dashboard |
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY |
Publishable key |
STRIPE_PRICE_ID |
One-time price ID for the e-book |
STRIPE_WEBHOOK_SECRET |
Webhook signing secret |
| Variable | Description |
|---|---|
EBOOK_PDF_PATH |
Optional override for PDF path (default: ./private/dev-brain.pdf) |
-
Install dependencies (pnpm):
pnpm install
-
Copy environment variables:
cp .env.example .env.local
-
Payhip — set
NEXT_PUBLIC_PAYHIP_PRODUCT_URLto your product link (see table above). The dev server and production build will not start without it. -
Stripe (optional, for download/session APIs)
- Create a Stripe account (test mode is fine).
- Under Developers → API keys, add
STRIPE_SECRET_KEYandNEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY. - Under Product catalog, create a product and a one-time price aligned with
SITE.priceUsdinlib/constants.ts; setSTRIPE_PRICE_IDto that price ID (price_...). - Set
NEXT_PUBLIC_APP_URLto your app origin.
-
E-book file
Place your PDF at
private/dev-brain.pdf, or setEBOOK_PDF_PATHto an absolute path. Theprivate/folder is gitignored except.gitkeep. -
Run the dev server:
pnpm dev
All e-book purchase CTAs use the same styled link to Payhip direct checkout (not the product page):
| Location | File | Label |
|---|---|---|
| Hero primary CTA | components/Hero.tsx |
“Get instant access — $17” |
| Pricing card | components/PricingCard.tsx |
“Get instant access — $17” |
| Cancel page | app/cancel/page.tsx |
“Continue to checkout” |
Implementation: lib/payhip.ts exports PAYHIP_CHECKOUT_URL ({origin}/buy?link={productKey}). components/BuyButton.tsx renders an <a href={PAYHIP_CHECKOUT_URL}> with existing Tailwind styles. No Payhip embed script.
For local testing, use the Stripe CLI to forward events:
stripe listen --forward-to localhost:3000/api/webhooks/stripeUse the printed webhook signing secret as STRIPE_WEBHOOK_SECRET in .env.local.
In production, add an HTTPS endpoint in the Stripe Dashboard pointing to https://your-domain/api/webhooks/stripe and set the signing secret in your host’s environment.
pnpm dev— development serverpnpm build— production buildpnpm start— run production buildpnpm lint— ESLint
- User clicks Buy → browser opens Payhip checkout (same as Payhip’s direct checkout link), not the product marketing page.
- Optional Stripe flow (if configured):
POST /api/checkout→ Stripe Checkout →/success?session_id=...→/api/download?session_id=...streamsprivate/dev-brain.pdf.
Replace placeholder Privacy, Terms, and Contact content before launch (see footer).
