The portfolio is structured around three distinct layers that communicate in one direction only — domain ← infrastructure ← presentation. The domain layer defines pure TypeScript contracts and logic with no external dependencies. The infrastructure layer is the only code that knows GitHub exists; it fetches, maps, and caches API data before handing it back as domain types. The presentation layer — React components and TanStack Router routes — consumes domain types exclusively and never reaches into the infrastructure layer directly. This separation makes each layer independently testable and keeps browser bundles free of server-only code.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/santiagonieto09/portafolio/llms.txt
Use this file to discover all available pages before exploring further.
Layers
Domain layer (src/domain/)
Framework-agnostic TypeScript interfaces and pure functions. This code has no knowledge of React, fetch, or the GitHub SDK. It contains:
github/types.ts— the canonical data contracts (PortfolioSnapshot,Profile,Repository,ActivityItem,LanguageSlice, etc.) shared by every other layer.github/language-colors.ts— a lookup map from language name to hex colour string, used for the language chart.github/technology-detection.ts— pure function that infers technology badges from repo name, description, topics, and language list.portfolio/repository-query.ts— pure filtering and sorting helpers for the repository explorer (search, language filter, sort order).
Infrastructure layer (src/infrastructure/github/)
The only code that knows the GitHub REST API exists. All files in this layer are server-only — the .server.ts filename suffix tells the Vite build to exclude them from the client bundle. It contains:
github-api.server.ts— the HTTP client (get<T>()), parallel fetch orchestration (fetchPortfolio()), per-repository enrichment (buildRepository()), and domain mapping. This is the sole file that makes outbound network requests toapi.github.com.snapshot-cache.ts— a dual-layer cache (in-memory + Cloudflare Cache API) that stores a serialisedPortfolioSnapshotand avoids redundant GitHub API calls across requests and cold starts.github-api-types.ts— raw GitHub REST response shapes (GitHubUser,GitHubRepo,GitHubEvent, etc.). These are provider contracts, intentionally kept separate from the domain types.
Presentation layer (src/components/, src/routes/)
React 19 components and TanStack Router file-based routes. This layer imports from the domain layer and from TanStack Query, but never from the infrastructure layer. The routes/index.tsx loader pre-populates the React Query cache server-side; components call useSuspenseQuery(portfolioQueryOptions()) and render from that cache.
The
.server.ts filename convention is enforced by the Vite build pipeline. Any attempt to import github-api.server.ts from a client-only module causes a build error at compile time, making it structurally impossible for API credentials or server-side logic to leak into the browser bundle.Request lifecycle
Browser or crawler requests /
An HTTP request arrives at the Nitro server runtime — either Vercel Edge on production or a Cloudflare Worker in the Lovable sandbox.
Nitro calls the TanStack Start loader
TanStack Router’s file-based routing matches the request to
src/routes/index.tsx and invokes its loader function with the shared QueryClient context.Loader calls ensureQueryData()
The loader calls
context.queryClient.ensureQueryData(portfolioQueryOptions()). The query function configured inside portfolioQueryOptions() is getPortfolio() — a TanStack Start server function defined in src/lib/portfolio.functions.ts — which executes on the server without an HTTP round-trip.Server function calls fetchPortfolio()
getPortfolio() dynamically imports and calls fetchPortfolio(GITHUB_USERNAME) from src/infrastructure/github/github-api.server.ts.Cache check via readSnapshot()
fetchPortfolio calls readSnapshot(). If a PortfolioSnapshot is found in memory or the Cloudflare Cache API and is within the 7-day TTL, it is returned immediately — no GitHub API calls are made.Cache miss: four parallel GitHub API requests
On a cache miss,
fetchPortfolio fires four requests simultaneously using Promise.all: user profile, repositories (up to 100, sorted by last update), social accounts, and public events. Repository language and release data are then fetched with a concurrency limit of 3 to avoid secondary rate limits.PortfolioSnapshot is built, cached, and returned
Raw API responses are mapped to domain types. The resulting
PortfolioSnapshot is written to both the in-memory store and the Cloudflare Cache API (with a 7-day max-age), then returned up the call stack.React Query cache is pre-populated
ensureQueryData stores the snapshot in the server-side QueryClient under the key ['portfolio'], making the data available to React components before rendering begins.React renders the page server-side
TanStack Start runs the full React component tree on the server. The
useSuspenseQuery call in PortfolioPage reads from the pre-populated cache — the Suspense boundary never shows a loading state. The complete HTML is streamed to the client.Runtime targets
The application’s server adapter is Nitro, which compiles the same application code to different deployment targets without any changes to the source.| Target | When active | Notes |
|---|---|---|
vercel | Production deploy | Uses Vercel’s Build Output API; outputs to .vercel/output. |
cloudflare | Lovable sandbox previews | Lovable’s @lovable.dev/vite-tanstack-config overrides the Nitro preset automatically. |
vite.config.ts default is nitro: { preset: "vercel" }. The Lovable sandbox overrides this with its own Cloudflare preset at build time, so preview deployments work identically to production deploys at the application level. The only runtime difference visible to the application is whether caches.default (Cloudflare Cache API) is available — handled gracefully by the snapshot cache module.