Nicholai's website
This is my personal website built with Next.js 15, Tailwind CSS, and TypeScript.
Tech Stack
Next.js 15, Tailwind CSS, Cabin font family, Turbopack, Typescript.
Prerequisites
- Node.js 18+ installed (node modules: densest thing in the known universe.)
- npm, yarn, pnpm, whatever honestly.
Installation
- Clone the repository:
git clone https://git.biohazardvfx.com/Nicholai/nicholais-website.git
- Install dependencies:
npm install
Development
Run the development server:
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
Open http://localhost:3000 with your browser to see the result.
Pushing to Git and Docker
-
Commit changed files to main branch on gitea
git add . && git commit -m "added higher res profile image" && git push origin main -
Build docker image
docker build -t git.biohazardvfx.com/nicholai/nicholais-website:latest . -
Push docker package to gitea
docker push git.biohazardvfx.com/nicholai/nicholais-website:latest
Building for Production
npm run build
Deployment
Docker Deployment
This project includes a Dockerfile for containerized deployment. To build and run the application using Docker:
- Build the image:
docker build -t nicholais-website .
- Run the container:
docker run -p 3000:3000 nicholais-website
- Or with environment variables:
docker run -p 3000:3000 -e NODE_ENV=production nicholais-website
Environment Variables
The application supports the following environment variables:
| Variable | Description | Default |
|---|---|---|
NODE_ENV |
Node.js environment | development |
PORT |
Port to run the server on | 3000 |
Blog Authoring (MDX)
You can write posts in MDX and have them show up on /blog.
- Local posts: add
.mdxfiles toapp/blog/posts/(these are always included). - Optional GitHub-backed posts: if you configure the
BLOG_*environment variables, MDX files from your GitHub repo are fetched and merged with local posts. GitHub posts override local ones on duplicate slugs.
Setup:
- Copy
.env.local.exampleto.env.localand fill in values. - For GitHub-backed content, set:
BLOG_REPO=owner/repoBLOG_PATH=path/to/mdx/folder(relative to repo root)BLOG_BRANCH=main(or your branch)GITHUB_TOKEN=(only required for private repos or higher rate limits)
Frontmatter template (put at the top of each .mdx file):
---
title: "Post Title"
publishedAt: "2025-01-15" # YYYY-MM-DD
summary: "One-liner summary"
tags: ["tag1", "tag2"]
image: "https://your.cdn/path-or-absolute-url.jpg"
---
Images:
- Prefer absolute URLs (CDN or repo raw URLs) to avoid build-time asset issues.
- The MDX renderer handles links and images for you (see
components/mdx.tsx).
How updates appear on the site:
- Incremental Static Regeneration (ISR): GitHub responses are cached with
next: { revalidate: BLOG_REVALIDATE_SECONDS }. New/edited posts appear automatically after the configured window (default 300s). - On-demand revalidation: trigger an immediate refresh via the
/api/revalidateendpoint (see below) or configure a GitHub webhook to call it on each push.
Blog Environment Variables
| Variable | Description | Default |
|---|---|---|
BLOG_REPO |
GitHub owner/repo containing your MDX files. Leave empty to use only local posts. |
— |
BLOG_PATH |
Path in the repo where .mdx files live (relative to repo root). |
app/blog/posts |
BLOG_BRANCH |
Branch name to read from. | main |
GITHUB_TOKEN |
GitHub token. Required for private repos or higher rate limits. | — |
BLOG_REVALIDATE_SECONDS |
ISR interval (seconds) for GitHub fetch cache. | 300 |
BLOG_CACHE_TAG |
Cache tag used for on-demand invalidation (revalidateTag). |
blog-content |
REVALIDATE_SECRET |
Shared secret for the revalidate API and GitHub webhook signature. | — |
On-demand Revalidation
An API route at /api/revalidate invalidates the blog listing and post pages, and also busts the cache tag used for GitHub content.
-
GET (simple manual trigger):
- Revalidate listing:
curl -sS "https://your-domain/api/revalidate?secret=REVALIDATE_SECRET" - Revalidate a specific post (by slug):
curl -sS "https://your-domain/api/revalidate?secret=REVALIDATE_SECRET&slug=my-post-slug" - Optionally revalidate additional paths:
curl -sS "https://your-domain/api/revalidate?secret=REVALIDATE_SECRET&path=/blog/my-post-slug"
- Revalidate listing:
-
POST (advanced, supports multiple slugs/paths):
curl -sS -X POST "https://your-domain/api/revalidate" \ -H "x-revalidate-secret: REVALIDATE_SECRET" \ -H "content-type: application/json" \ -d '{ "slugs": ["my-post-slug"], "paths": ["/blog", "/blog/my-post-slug"] }' -
GitHub Webhook (recommended):
- In your repo settings, add a Webhook:
- Payload URL:
https://your-domain/api/revalidate - Content type:
application/json - Secret: set to the same value as
REVALIDATE_SECRET - Event: “Just the push event”
- Payload URL:
- On push, the webhook sends changed file paths. The API will:
- Revalidate the
BLOG_CACHE_TAGto refresh GitHub fetches. - Revalidate
/blogand any changed post slugs underBLOG_PATH.
- Revalidate the
- In your repo settings, add a Webhook:
Security notes:
- Do not expose
REVALIDATE_SECRETpublicly. For manual GET usage, keep the URL private. - For CI/CD, use the POST form with the
x-revalidate-secretheader.
License
This project is open source, take it. I don't give a fuck. I am not your dad.
Author
Nicholai - VFX Supervisor & Developer
- Website: nicholai.work
- Email: nicholai@biohazardvfx.com
- Instagram: @nicholai.exe