Skip to content

mattblackdev/mattblackio

Repository files navigation

mattblack.io

Personal portfolio website for Matt Black — senior software engineer specializing in full-stack web and mobile development.

Built with Next.js (Pages Router), Tailwind CSS, and Framer Motion. Deployed on Netlify.

Development

yarn install
yarn dev

Open http://localhost:3000.

Build

yarn build
yarn start

Content

Resume content lives in utils/section-data.js. Site-wide settings (name, bio, contact) are in utils/global-data.js and utils/site-config.js.

Environment variables

Variable Description
NEXT_PUBLIC_SITE_URL Canonical site URL (default: https://mattblack.io)
BLOG_NAME Display name
BLOG_JOB_TITLE Job title for SEO / structured data
BLOG_FOOTER_TEXT Footer text
BLOG_EMAIL Contact email
BLOG_LINKEDIN LinkedIn profile URL
OPENAI_API_KEY Required for the résumé chat assistant. Server-side only — never exposed to the browser.
OPENAI_MODEL Optional. Chat model for the assistant (default: gpt-4o-mini).
CHAT_RATE_LIMIT_PER_MINUTE Optional. Max chat requests per visitor IP per minute (default: 8).
CHAT_RATE_LIMIT_PER_DAY Optional. Max chat requests per visitor IP per day (default: 40).
CHAT_GLOBAL_DAILY_LIMIT Optional. Max chat requests across all visitors per day (site-wide circuit breaker, default: 1000).
NEXT_PUBLIC_CHAT_MESSAGE_LIMIT Optional. Soft per-browser daily message cap enforced in the UI (default: 20).

Résumé chat assistant

The homepage includes a subtle floating action button (bottom-right) that opens a chat window powered by components/ChatWidget.js. Questions are sent to the serverless route pages/api/chat.js, which grounds the LLM in the résumé content (via utils/resume-context.js) and instructs it to answer only questions about Matt's background, declining anything off-topic.

To enable it, set OPENAI_API_KEY in your environment:

  • Local: add it to a .env.local file (git-ignored): OPENAI_API_KEY=sk-...
  • Netlify: add it under Site settings → Environment variables. Netlify's Next.js runtime runs pages/api/* as serverless functions automatically.

To use a different provider (e.g. Anthropic), swap the request logic in pages/api/chat.js.

Abuse protection & cost control

The assistant has several layers of protection against abuse and runaway API costs:

  1. Per-visitor rate limitingpages/api/chat.js limits each client IP (per-minute and per-day) via utils/rate-limit.js and returns 429 Too Many Requests with a Retry-After header when exceeded.
  2. Site-wide daily circuit breaker — a global daily request cap (CHAT_GLOBAL_DAILY_LIMIT) stops serving once the whole site hits its ceiling for the day.
  3. Client-side daily cap — the widget caps messages per browser per day (NEXT_PUBLIC_CHAT_MESSAGE_LIMIT) to curb casual/accidental overuse and give clear feedback.
  4. Bounded requests — conversation history, per-message length, and max_tokens are all capped so no single request can be expensive.

Important

The in-memory rate limiter is best-effort: on serverless (Netlify Functions) counters live in a single warm instance and reset on cold starts, so they are not shared across concurrent instances. The guaranteed ceiling on your bill is an OpenAI account-level spend limit — set a monthly budget and usage limit under platform.openai.com → Settings → Limits. For strict, shared rate limiting, back utils/rate-limit.js with a durable store such as Upstash Redis.

About

No description, website, or topics provided.

Resources

License

Stars

0 stars

Watchers

1 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors