Skip to content

Latest commit

 

History

465 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

👨‍💻 @diegocoxta/sites

My personal webpages powered by Next.js, TypeScript and CSS Modules.

A single Next.js app serves all three domains. proxy.ts inspects the request host and rewrites /{pathname} to /{domain}/{pathname}, so each domain resolves to its own route tree under app/. Per-domain settings (title, links, locales, theme) live in app/<domain>/config.ts.

🖥️ Tech Stack

  • Next.js 16 (App Router, Turbopack) with TypeScript.
  • Content in Markdown via next-mdx-remote + gray-matter, read through lib/content.ts.
  • Internationalization with a small custom setup in lib/i18n/ — edge-safe locale negotiation (used by proxy.ts) plus a server-only dictionary loader; JSON translations live under public/<domain>/translations/.
  • Command bar with kbar (⌘K / Ctrl+K).
  • Dark mode with next-themes.
  • RSS feed generation with rss.
  • "Recent activity" widgets that pull from Discogs, Letterboxd, Unsplash, Hardcover, GitHub, Last.fm, Setlist.fm and Deezer (lib/services/).
  • ESLint, Prettier, stylelint, Husky and lint-staged — and more.

📁 Project Structure

app/
  <domain>/           route tree for one domain (e.g. diegocosta.com.br)
    config.ts         per-domain config (title, links, locales, theme)
    [...page]/        catch-all for Markdown pages; notFound() when missing
    not-found.tsx     404 boundary (single-locale domains)
    [locale]/         localized routes (multi-locale domains only)
      not-found.tsx   localized 404 boundary (diegocoxta.com)
  config.ts           shared config defaults (theme)
components/            React components (one folder each, co-located CSS Modules)
lib/
  config.ts           shared types for the per-domain config
  content.ts          Markdown reading, front-matter, locale fallback (contentFor)
  public-path.ts      helper for building public/<domain>/… paths
  i18n/
    locale.ts         locale list + Accept-Language / cookie negotiation (edge-safe)
    translator.ts     createTranslator: lookup, {param} interpolation, dates
    messages.ts       loads public/<domain>/translations/<locale>.json (server-only)
  services/            third-party integrations for the activity widgets
proxy.ts              host-based rewrite + locale negotiation (Next middleware)
public/<domain>/       Markdown content (posts/, pages/), translations/ and assets

The ~/* import alias maps to the repo root (see tsconfig.json).

📋 Requirements

⌨️ Developing

  1. Clone this repo with git.

  2. Run yarn install inside the project directory.

  3. Copy the env file: cp .env.example .env. Every key is optional — without a given token the matching activity widget just renders empty. SITE_ACCENT_COLOR and SITE_TEXT_COLOR control the theme colors.

  4. Start the dev server for one domain:

    • yarn dev:br → diegocosta.com.br
    • yarn dev:me → diegocosta.me
    • yarn dev:com → diegocoxta.com

    (or the full form, e.g. yarn dev:diegocosta.com.br)

  5. Open http://localhost:3000. The DEV_SITE env var set by these scripts tells proxy.ts which domain localhost maps to.

Useful scripts

Script Description
yarn build Production build
yarn start Serve the production build
yarn lint Run ESLint
yarn prettier Format the codebase
yarn stylelint Lint & fix CSS Modules
yarn upgrade-latest Interactive dependency upgrade

🌐 Content & i18n

Content is plain Markdown under public/<domain>/, split into posts/ and pages/ (one folder per slug). Localized files use an index.<locale>.md suffix (e.g. index.en.md) and fall back to the default locale, then to index.md. Relative image references (![](./img.png)) are rewritten to servable paths at read time.

UI strings are flat-key JSON dictionaries at public/<domain>/translations/<locale>.json. A requested locale is merged over the default-locale dictionary, so partial translations are fine. Keys prefixed client. / config. / components. / page. go through the translator; anything else is treated as a literal (brand and proper names). Keys prefixed client. are the subset shipped to Client Components, via getClientMessages in lib/i18n/messages.ts.

diegocoxta.com ships in pt, en and es; the other domains are single-locale (and skip the /<locale> path prefix entirely).

404s

Unmatched URLs are funneled through each domain's catch-all [...page] route, which calls notFound(). There is no root app/not-found.tsx — it would need the request host to choose a domain, forcing every catch-all to render dynamically — so each domain carries its own boundary:

  • Single-locale domains render app/<domain>/not-found.tsx (a Server Component).
  • diegocoxta.com places the boundary at app/diegocoxta.com/[locale]/not-found.tsx so it renders inside the [locale] layout and can localize: a Client Component that reads the translator context. Its copy is stored under a client. key (client.components.notFound.message) so getClientMessages ships it.

The catch-all routes are dynamic, so the styled body streams in on hydration; the response status is 404 either way.

🚀 Deployment

Deployed on Vercel. Each domain is attached as a custom domain to the same project; the host-based rewrite in proxy.ts does the routing.

📐 Contributing

See CONTRIBUTING.md for details.

☕ Get in touch

Say hello on Bluesky, Mastodon or email.

📜 License

Source code is MIT. The publications under public/** are © Diego Costa, all rights reserved.

About

My personal webpages powered by next, typescript and CSS modules.

Topics

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Used by

Contributors

Languages