Why a backend engineer built a portfolio in React Native, how Expo Router exports it as a static website with real HTML and metadata, and how the live API playground talks to the backend.
Most of my work happens behind an API, so I wanted my portfolio to show the other side too: a front end that consumes a real backend. I built it with React Native and Expo. The same code is exported as a static website and can also run as an iOS and Android app.
Why React Native for a website?
With React Native for Web, components like View, Text and Pressable render to DOM elements in the browser and to native views on a phone. Expo Router adds file-based routing that works on both. For a portfolio that means one set of components for the site and a possible app, written in TypeScript with the same patterns I would use for a mobile client of my API.
Static rendering: real HTML, not an empty div
A single-page app that renders everything in JavaScript is bad for search engines and for link previews on LinkedIn. Expo Router can pre-render every route to HTML at build time:
json
{ "expo": { "web": { "output": "static" } } }
npx expo export --platform web then writes one HTML file per route into dist/, ready for any static host.
Dynamic routes, such as articles and project case studies, list their pages with generateStaticParams:
tsx
export function generateStaticParams() {
return articles.map((article) => ({ slug: article.slug }));
}
and each page provides its title, description and Open Graph tags with generateMetadata, which ends up in the static HTML before any JavaScript runs:
tsx
export function generateMetadata(_request: unknown, params: { slug: string }): Metadata {
const article = articleBySlug(params.slug);
return { title: article.title, description: article.description };
}
Content as typed data
Experience, projects, publications and articles live in TypeScript files. The compiler checks that every project has a category, every article a date, and every link to a case study points at a project that exists. Article bodies use a small Markdown subset that I parse into blocks and render with native components, so they look the same on every platform without a WebView.
Responsive layout with Flexbox
React Native has no media queries, but useWindowDimensions gives the width, and Flexbox does the rest. A few breakpoints decide the number of columns; everything else is flexWrap, gap and a maximum content width. The navigation becomes a menu button below tablet width.
The live API playground
The playground calls my Auction Platform API: list live auctions, read one, log in as a demo buyer and place a bid. The base URL comes from an environment variable that is inlined at build time:
ts
const API_URL = process.env.EXPO_PUBLIC_API_URL;
The API runs on a free plan that sleeps when idle, so each request has a timeout. If the API is unreachable, the playground switches to a built-in simulator that applies the same bidding rules to the same demo data, and says so in the status line. A visitor always sees a working demo, and never mistakes the simulation for the real thing.
On the API side, CORS allows the portfolio's origin, and the bid endpoint accepts an Idempotency-Key, so a double tap in the playground never places two bids.
What I would do the same way again
▸
Static export and generateMetadata from day one: link previews and search results just work.
▸
Content in typed files rather than a CMS, for a site with one author.
▸
A graceful fallback for every network call on a page whose first job is to make a good impression.