A small, fast personal site built with Next.js (App Router). Posts live as Markdown on disk with front matter; pt-BR and English URLs share one content tree, with on-demand translation when the viewer’s locale differs from how a note was written.
| Content | Markdown + gray-matter front matter, Git-friendly |
| Locales | pt-BR (default) and en; / redirects to /{lang} (cookie-backed preference where set) |
| Translation | Optional OpenAI, or MyMemory fallback; results cached under .translate-cache/ |
| Discovery | In-site search across titles and excerpts |
| Reader UX | GFM Markdown, wiki-style links between notes, tags from #hashtags and [[links]] |
| PWA | Service worker registered in production |
| Layer | Stack |
|---|---|
| Framework | Next.js 16 (App Router), React 19 |
| Language | TypeScript 5 |
| Content | Markdown on disk · gray-matter (front matter) · react-markdown + remark-gfm (GFM) |
| i18n & translation | Locale routing + dictionary strings · OpenAI Chat Completions (optional) · MyMemory (fallback) · on-disk cache (.translate-cache/) |
| Tooling | ESLint 9 · eslint-config-next |
Typography (loaded in the root layout): Inter, Geist Mono, JetBrains Mono, IBM Plex Mono, Fira Code (Google Fonts).
- Node.js 20+ (recommended for current Next.js releases)
- npm, pnpm, yarn, or bun — examples below use
npm
npm install
npm run devOpen http://localhost:3000 — you’ll be redirected to http://localhost:3000/pt-BR unless a saved language applies.
Production build:
npm run build
npm start- Put a
.mdfile incontent/<source-locale>/(see Environment forCONTENT_SOURCE_LOCALE). - The URL slug is the filename without
.md(e.g.keyboard.md→/pt-BR/post/keyboard). - Front matter (YAML between
---lines):
---
title: Title shown in listings and OG
date: 2026-03-12 # ISO date
tags:
- one-tag
- another
excerpt: Optional short blurb for cards and search
minutes: # Optional; otherwise estimated from word count
contentLang: pt-BR # Optional override if prose language ≠ folder locale
---Body is standard Markdown (GFM). Code fences are stripped when inferring hashtags so examples don’t pollute tags.
Links between notes: use wikilinks, e.g. [[keyboard]] or [[Displayed title|actual-slug]], matching another post’s file slug.
| Variable | Required | Purpose |
|---|---|---|
NEXT_PUBLIC_SITE_URL |
Production | Canonical base URL (sitemap, Open Graph, JSON-LD). Falls back to VERCEL_URL or localhost. |
CONTENT_SOURCE_LOCALE |
No | pt-BR or en — which folder under content/ is the source for all posts. Default: pt-BR. |
OPENAI_API_KEY |
No | If set, translation uses OpenAI; on failure it falls back to MyMemory. |
OPENAI_TRANSLATE_MODEL |
No | Model id (default gpt-4o-mini). |
Translation cache is written to .translate-cache/ (gitignored). Copying the cache across deploys avoids repeat API calls.
content/ # Markdown sources (by locale folder)
src/
app/ # Routes, layouts, metadata, sitemap, robots
components/ # UI (header, feed, post, search, …)
lib/
content/load-posts.ts # Read & normalize posts
translate.ts # Cross-locale body translation
locales.ts, dictionary.ts
middleware.ts # Redirect `/` → `/{locale}`
legacy/ # Older site/materials (see legacy/README.md)
| Command | Description |
|---|---|
npm run dev |
Development server |
npm run build |
Production build |
npm run start |
Serve production output |
npm run lint |
ESLint (Next.js config) |
- Set
NEXT_PUBLIC_SITE_URLto your public HTTPS origin. OPENAI_API_KEYimproves translation quality; without it the app uses MyMemory (free tier limits apply).- The service worker is only active when
NODE_ENV=production.
This repo targets a recent Next.js generation with API and conventions that may differ from older docs. If you use automation or AI-assisted edits, skim AGENTS.md and Next’s shipped docs under node_modules/next/dist/docs/ before large changes.