Skip to main content
Back to Devlog

Frontend // READY

Next.js Portfolio Architecture

A portfolio becomes easier to maintain when projects, posts, links, and stack modules are data-driven. That principle shaped every architectural decision in LookADev — from file structure to deployment strategy.

The site is a single Next.js application with no CMS, no database, and no external API calls at build time. All content lives in TypeScript data files: site.ts for projects and devlog posts, brand.ts for computed portfolio metadata, i18n.ts for bilingual strings. Adding a new project means adding an object to an array and pushing to GitHub.

I chose this over a headless CMS for one reason: speed of iteration. When I finish building a new client project at 2am, I want to add it to my portfolio in under five minutes — not log into a CMS, create a content model, upload assets, and wait for webhooks. A TypeScript object with autocomplete is faster than any admin panel.

The component architecture follows a layered pattern. AppShell handles the global layout (header, footer, scanline overlay). Page-level components like ProjectGrid and DevLogList consume data arrays and render lists. UI primitives (RetroWindow, ArrowIcon, PageIntro) are stateless and composable. No component fetches its own data — everything flows down from page-level props or shared data imports.

Internationalization was a deliberate trade-off. Instead of a full i18n framework with locale routing, I built a simple context provider that swaps between PT and EN string dictionaries. The language toggle lives in the header and persists via localStorage. Project descriptions have both summary (EN) and summary_pt fields. This covers 95% of what a portfolio needs without the routing complexity of /en/projects vs /pt/projects.

The retro 'cartridge' metaphor for project cards wasn't just visual. It informed the data model. Each project has a fileName (like PROJECT_01.CART), a status (LIVE, PUBLIC, EXPERIMENTAL), an accent color, and a caseStudy array with labeled sections. The card component renders differently based on status — live projects get a pulsing indicator, public repos get a GitHub link, experimental projects get a lab badge.

Static generation with generateStaticParams means every project page and devlog article is pre-rendered at build time. The site loads fast because there's no client-side data fetching — just hydrated HTML with interactive elements. Lighthouse scores stay above 95 across all categories.

One pattern I'd recommend to any developer building a portfolio: separate your data from your presentation completely. When your content is just TypeScript objects, you can filter, sort, search, and compute derived data (featured projects, recent posts, stack categories) without any runtime overhead.

Building notes by Lucas Martins, founding developer at LookADev.

Read more notes