This document describes every environment variable used in this project, grouped by module. Each entry includes the variable's purpose, where it's used, and where to get the value.
- GitHub integration (contribution graph, API access)
- DMCA protection badge
- Analytics (PostHog, OpenPanel, Google Analytics, Umami, Vercel Analytics)
- Bot protection / anti-spam (reCAPTCHA, hCaptcha)
- SEO & site verification (Google, Bing, canonical URL)
- Social share / Open Graph
- Contact form / transactional email
- Coding stats widget (WakaTime)
- Blog / CMS integration
- Guestbook
- Newsletter signup
- Security & performance (rate limiting, cron protection, ISR revalidation, image domains)
- Cookie consent / GDPR
- Maintenance mode
- Resume/CV download
- Dynamic
robots.txt&sitemap.xmlsetup
- Anyone setting up this project locally β copy
.env.exampleto.env.localand fill in the values described here. - Anyone deploying this project (Vercel, VPS, etc.) β use this as the checklist for which secrets/keys to configure in the hosting provider's environment settings.
- Contributors β use this to understand what each integration does before touching related code.
- Future you β as a reference when a module is added, removed, or a key rotates.
You don't need to be an expert in every service listed here β this guide is written so a beginner-to-intermediate Next.js developer can follow it without prior experience with any specific provider (PostHog, Resend, Upstash, etc.). That said, it helps most if you:
| If you are... | This guide helps you... |
|---|---|
| π New to the project | Understand what each variable does before blindly copy-pasting .env.example |
| π οΈ Setting up locally for the first time | Know exactly which service to sign up for and where to grab each key |
| π Deploying to production | Confirm nothing is missing before the build fails on a hosting platform |
| π€ Contributing a PR | Add a new integration following the same documentation pattern |
| π Rotating a leaked/expired key | Quickly find which file/route uses that variable |
If a term here is unfamiliar (e.g. "ISR", "REST token"), each entry links its purpose in plain language β you don't need prior knowledge of the specific tool to understand why the variable exists.
- GitHub Integration
- DMCA
- Analytics β PostHog
- Analytics β OpenPanel
- Bot Protection / Anti-Spam
- SEO / Site Verification
- Additional Analytics Options
- Social Share / Open Graph
- Contact Form / Email
- WakaTime (Coding Stats)
- Blog / CMS
- Guestbook
- Newsletter
- Security & Performance
- Cookie Consent / GDPR
- Maintenance Mode
- Resume / CV
- robots.txt & sitemap.xml
- Full
.env.example
- Purpose: Personal access token for authenticating GitHub API requests.
- Used for: Increasing API rate limits, fetching private repo/profile data.
- Used in: Server-side API calls (e.g.
/api/github/*routes). - Get it from: GitHub β Settings β Developer settings β Personal access tokens.
- Purpose: Base URL of a third-party API (jogruber.de) that returns GitHub contribution graph data.
- Used for: Rendering the contribution heatmap on the profile/portfolio page.
- Used in: Client or server fetch call that populates the heatmap component.
GITHUB_API_TOKEN=
GITHUB_CONTRIBUTIONS_API_URL=- Purpose: URL for the DMCA protection badge/link.
- Used for: Displaying a "DMCA Protected" badge, typically in the footer.
- Used in: Footer component (client-side, hence
NEXT_PUBLIC_).
NEXT_PUBLIC_DMCA_URL=- Purpose: Project token that identifies your PostHog project.
- Used for: Tracking user behavior (page views, events, funnels).
- Used in: Client-side PostHog SDK initialization.
- Purpose: Host URL of the PostHog instance.
- Used for: Pointing the SDK to either PostHog Cloud or a self-hosted instance.
- Used in: Same PostHog SDK init as above.
NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN=
NEXT_PUBLIC_POSTHOG_HOST=- Purpose: Client ID for OpenPanel's client-side SDK.
- Used for: Browser-side event tracking.
- Used in: OpenPanel provider/init on the frontend.
- Purpose: Identifies which OpenPanel project the server should report to.
- Used for: Server-side analytics calls.
- Used in: Backend API routes sending events.
- Purpose: Server-side client ID (separate from the public one) for authenticated server requests.
- Used for: Server-to-server OpenPanel API auth.
- Used in: Backend event-sending logic.
- Purpose: Secret key that authorizes server-side OpenPanel API access.
- Used for: Sending events or reading data from the backend securely.
- Used in: Backend API routes, never exposed to the client.
NEXT_PUBLIC_OPENPANEL_CLIENT_ID=
OPENPANEL_PROJECT_ID=
OPENPANEL_CLIENT_ID=
OPENPANEL_CLIENT_SECRET=- Purpose: Server-side secret to verify reCAPTCHA tokens.
- Used for: Validating form submissions aren't from bots.
- Used in: API route that verifies the reCAPTCHA response.
- Purpose: Public site key that renders the reCAPTCHA widget.
- Used for: Loading the reCAPTCHA challenge in the browser.
- Used in: Contact/comment forms on the frontend.
- Purpose: Public site key for hCaptcha, a privacy-friendlier alternative to reCAPTCHA.
- Used for: Rendering the hCaptcha widget client-side.
- Used in: Forms configured to use hCaptcha instead of/alongside reCAPTCHA.
- Purpose: Server-side secret to verify hCaptcha responses.
- Used for: Validating hCaptcha tokens on submission.
- Used in: API route that verifies hCaptcha responses.
RECAPTCHA_SECRET_KEY=
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=
NEXT_PUBLIC_HCAPTCHA_SITE_KEY=
HCAPTCHA_SECRET=- Purpose: Verification code proving site ownership to Google.
- Used for: Enabling Google Search Console access for this domain.
- Used in:
<meta>tag in the root layout/head, or viametadata.verificationin Next.js. - Get it from: Google Search Console β Settings β Ownership verification β HTML tag method.
- Purpose: Verification code proving site ownership to Bing.
- Used for: Enabling Bing Webmaster Tools access.
- Used in:
<meta>tag in the root layout/head. - Get it from: Bing Webmaster Tools β Site verification.
- Purpose: The canonical base URL of the deployed site.
- Used for: Building absolute URLs for sitemap.xml, robots.txt, canonical tags, and OG images.
- Used in:
app/sitemap.ts,app/robots.ts, metadata generation.
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION=
NEXT_PUBLIC_BING_SITE_VERIFICATION=
NEXT_PUBLIC_SITE_URL=- Purpose: Google Analytics 4 measurement ID.
- Used for: Tracking pageviews/events via GA4.
- Used in: GA script loaded in the root layout.
- Purpose: Website ID for Umami, a self-hosted privacy-focused analytics tool.
- Used for: Tracking traffic without third-party cookies.
- Used in: Umami tracking script in the root layout.
- Purpose: Flag/config to enable Vercel Analytics.
- Used for: Built-in Vercel traffic and Web Vitals tracking.
- Used in:
@vercel/analyticscomponent in the root layout.
NEXT_PUBLIC_GA_MEASUREMENT_ID=
NEXT_PUBLIC_UMAMI_WEBSITE_ID=
NEXT_PUBLIC_VERCEL_ANALYTICS=- Purpose: Endpoint that dynamically generates Open Graph preview images.
- Used for: Giving each page a unique social-share preview image.
- Used in:
generateMetadata/opengraph-imagefor each route.
- Purpose: Your Twitter/X handle.
- Used for: Populating the
twitter:site/twitter:creatormeta tags. - Used in: Root metadata / Twitter card config.
NEXT_PUBLIC_OG_IMAGE_API=
NEXT_PUBLIC_TWITTER_HANDLE=- Purpose: API key for Resend, used to send transactional email.
- Used for: Delivering messages submitted through the "Contact Me" form.
- Used in: API route that handles the contact form submission (e.g.
/api/contact). - Get it from: Resend dashboard β API Keys.
- Purpose: The email address that receives contact-form submissions.
- Used for: Routing incoming messages to your inbox.
- Used in: Same contact-form API route, as the recipient address.
- Purpose: The "From" address shown to the sender/service.
- Used for: Sending the contact-form confirmation or notification email.
- Used in: Resend send call in the contact-form API route.
RESEND_API_KEY=
EMAIL_TO=
EMAIL_FROM=- Purpose: API key that authorizes requests to WakaTime.
- Used for: Pulling your coding activity stats (hours coded, top languages) to show a "Coding Activity" widget.
- Used in: Server-side fetch that feeds the stats widget, usually paired with the GitHub contribution graph.
- Get it from: WakaTime β Settings β API Key.
WAKATIME_API_KEY=- Purpose: Identifies your Contentful space (or swap for Sanity/Notion/MDX equivalent).
- Used for: Fetching blog posts/content for the blog section.
- Used in: CMS client initialization, used by blog listing/detail pages.
- Purpose: Access token authorizing reads from the CMS.
- Used for: Authenticating content fetches from Contentful's API.
- Used in: Same CMS client as above.
CONTENTFUL_SPACE_ID=
CONTENTFUL_ACCESS_TOKEN=If you use a different CMS (Sanity, Notion API, or local MDX files), swap these two for that provider's equivalent credentials.
- Purpose: Connection string to a Postgres/Supabase database.
- Used for: Storing visitor guestbook entries (name, message, timestamp).
- Used in: Guestbook API route (create/list entries) and its ORM client.
DATABASE_URL=If a guestbook is the only feature needing a database, this can be the same
DATABASE_URLalready used elsewhere in the project β no need to duplicate it.
For a portfolio, the simplest and most maintainable option is Resend Audiences (Resend's built-in newsletter/broadcast feature) β since the contact form above already uses Resend, this avoids adding a second email provider. Alternatives like ConvertKit or Buttondown work too, but Resend keeps the stack to one API key.
- Purpose: ID of the Resend Audience (subscriber list) that collects newsletter signups.
- Used for: Adding new subscribers when someone submits the newsletter signup form.
- Used in: API route for the newsletter signup form (e.g.
/api/newsletter/subscribe). - Get it from: Resend dashboard β Audiences β create an audience, copy its ID.
RESEND_AUDIENCE_ID=Reuses
RESEND_API_KEYfrom the Contact Form section above β no separate API key needed.
- Purpose: REST endpoint for an Upstash Redis instance.
- Used for: Rate limiting the Contact Form and Newsletter signup endpoints to prevent spam/abuse.
- Used in: Middleware or API route wrapping form submission handlers.
- Purpose: Auth token for the Upstash Redis REST API.
- Used for: Authenticating rate-limit read/write calls.
- Used in: Same rate-limiting logic as above.
- Purpose: Shared secret that verifies scheduled/cron requests are legitimate.
- Used for: Protecting a cron endpoint (e.g. periodically refreshing GitHub contribution data) from being triggered by anyone who finds the URL.
- Used in: The cron API route, checked against the
Authorizationheader.
- Purpose: Secret that authorizes manual ISR (Incremental Static Regeneration) triggers.
- Used for: Instantly refreshing a page's static content when a new blog post is published in the CMS.
- Used in:
/api/revalidateroute, called by a CMS webhook on publish.
- Purpose: Comma-separated list of external domains allowed for
next/imageoptimization. - Used for: Whitelisting image sources like the CMS, GitHub avatars, or OG image API.
- Used in:
next.config.jsβimages.remotePatterns/images.domains.
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
CRON_SECRET=
REVALIDATE_SECRET=
NEXT_PUBLIC_IMAGE_DOMAINS=- Purpose: ID for a cookie-consent provider (CookieYes, Cookiebot, or similar).
- Used for: Showing a cookie consent banner and blocking analytics scripts (PostHog, GA, Umami) until the visitor consents β required for GDPR compliance with EU visitors.
- Used in: Root layout, loaded before other analytics scripts.
NEXT_PUBLIC_COOKIEYES_ID=- Purpose: Boolean flag to toggle site-wide maintenance mode.
- Used for: Showing a "Site under maintenance" page instead of the normal site while you're making major changes.
- Used in: Root layout or middleware, checked before rendering normal pages.
NEXT_PUBLIC_MAINTENANCE_MODE=- Purpose: Direct link to your resume/CV file (hosted on Google Drive, S3, or a CDN).
- Used for: Powering the "Download Resume" button on the portfolio.
- Used in: Hero/About section download button.
NEXT_PUBLIC_RESUME_URL=A static public/robots.txt applies the same rules to every environment. Using a dynamic app/robots.ts instead lets production and staging behave differently β so a staging deployment doesn't accidentally get indexed by Google.
// app/robots.ts
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
const isProd = process.env.NEXT_PUBLIC_ENV === 'production'
return {
rules: {
userAgent: '*',
allow: isProd ? '/' : '',
disallow: isProd ? ['/api/', '/admin/'] : '/',
},
sitemap: `${process.env.NEXT_PUBLIC_SITE_URL}/sitemap.xml`,
}
}- Purpose: Identifies the current deployment environment.
- Used for: Switching robots.txt rules (allow-all in production, disallow-all elsewhere).
- Used in:
app/robots.ts.
NEXT_PUBLIC_SITE_URL(already listed under SEO above) is reused here to build the sitemap URL, and again inapp/sitemap.tsto build each page's absolute URL.
NEXT_PUBLIC_ENV= # production / staging / development# GitHub Integration
GITHUB_API_TOKEN=
GITHUB_CONTRIBUTIONS_API_URL=
# DMCA
NEXT_PUBLIC_DMCA_URL=
# Analytics β PostHog
NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN=
NEXT_PUBLIC_POSTHOG_HOST=
# Analytics β OpenPanel
NEXT_PUBLIC_OPENPANEL_CLIENT_ID=
OPENPANEL_PROJECT_ID=
OPENPANEL_CLIENT_ID=
OPENPANEL_CLIENT_SECRET=
# Bot Protection / Anti-Spam
RECAPTCHA_SECRET_KEY=
NEXT_PUBLIC_RECAPTCHA_SITE_KEY=
NEXT_PUBLIC_HCAPTCHA_SITE_KEY=
HCAPTCHA_SECRET=
# SEO / Site Verification
NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION=
NEXT_PUBLIC_BING_SITE_VERIFICATION=
NEXT_PUBLIC_SITE_URL=
# Analytics (Additional)
NEXT_PUBLIC_GA_MEASUREMENT_ID=
NEXT_PUBLIC_UMAMI_WEBSITE_ID=
NEXT_PUBLIC_VERCEL_ANALYTICS=
# Social Share / Open Graph
NEXT_PUBLIC_OG_IMAGE_API=
NEXT_PUBLIC_TWITTER_HANDLE=
# Contact Form / Email
RESEND_API_KEY=
EMAIL_TO=
EMAIL_FROM=
# WakaTime (Coding Stats)
WAKATIME_API_KEY=
# Blog / CMS
CONTENTFUL_SPACE_ID=
CONTENTFUL_ACCESS_TOKEN=
# Guestbook
DATABASE_URL=
# Newsletter (reuses RESEND_API_KEY above)
RESEND_AUDIENCE_ID=
# Security & Performance
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
CRON_SECRET=
REVALIDATE_SECRET=
NEXT_PUBLIC_IMAGE_DOMAINS=
# Cookie Consent / GDPR
NEXT_PUBLIC_COOKIEYES_ID=
# Maintenance Mode
NEXT_PUBLIC_MAINTENANCE_MODE=
# Resume / CV
NEXT_PUBLIC_RESUME_URL=
# Environment
NEXT_PUBLIC_ENV=