Ryan’s Website
-
| [V1 |
Deployed Thru Github Pages](https://ryantren.github.io/portfolio-website/) |
-
| [V2 |
-WIP- SCRAPPED](https://github.com/RyanTren/portfolio-website/tree/v2) |
-
| [V3 |
Improved with V4 ](https://github.com/RyanTren/portfolio-website/tree/v3) |
-
| [V4 |
Next.js, Deployed with Vercel](https://portfolio-site-swart-xi.vercel.app/) |
What’s in V4?
V4 is v0’s build for my portfolio website :p
libraries?
animations?
Rendering Architecture
Route Tree
src/app/layout.tsx (Server Component)
- Defines
<html> shell, injects Geist fonts via inline <style>, registers globals.css, sets metadata.
src/app/page.tsx (Client Component)
"use client" at top; renders the entire homepage as a single interactive page with multiple sections (hero, about, projects, experience, contact, footer).
SSR/CSR Boundaries
- The layout is server-rendered by default, enabling metadata and global CSS without client overhead.
- The page is fully client-rendered to support:
- Interval-based role/gallery rotation (
useEffect)
- Imperative scroll (
scrollIntoView, window.scrollTo)
- Window
open for external links and resume
- UI state for filters and gallery selection
Data Model
src/data/profile.ts
- Defines typed interfaces for the portfolio domain:
SocialLink, ExperienceItem, ProjectItem, ProfileData
- Exports a
profile: ProfileData object with sample content (name, tagline, socials, experiences, projects, skills, gallery).
- Intended usage:
- Hydrate the page or server components from a single source of truth.
- Enables future server data fetching or CMS replacement without changing UI contracts.
- Current state:
src/app/page.tsx uses local arrays for projects/experiences, not the profile export.
UI System
Styling and Theming
globals.css:
- Tailwind v4 directives and
tw-animate-css.
- CSS variables define color system, radii, and chart/sidebar palettes for both light and
.dark modes.
- Global base layer applies
bg-background and text-foreground.
- Fonts:
- Geist Sans/Mono via
next/font in layout.tsx.
- Variables
--font-geist-sans, --font-geist-mono used by Tailwind tokens.
shadcn-inspired Primitives
components/ui/* primitives are written as typed functional components:
badge.tsx, button.tsx use cva for variants and @radix-ui/react-slot for asChild composition.
card.tsx provides a composable primitive set: Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter, CardAction.
input.tsx, textarea.tsx provide consistent form styling and focus/invalid rings.
- Utility:
lib/utils.ts provides cn(...inputs) using clsx + tailwind-merge to dedupe class conflicts.
Component Architecture
Page Composition (src/app/page.tsx)
- Imports UI primitives and
lucide-react icons.
- Local state:
currentRole (rotating role display)
selectedImage (rotating gallery hero image)
selectedCategory (projects filter — UI placeholder; filtering logic present)
- Effects:
- Intervals for role rotation (2s) and gallery rotation (4s).
- DOM interactions:
scrollToSection(id) with smooth scroll
scrollToTop helper
- External links via
window.open
- Sections:
- Hero: headline, rotating role, CTAs (view work, resume download), social buttons
- About: image gallery, mini-thumbnails, interests, fun facts
- Projects: placeholder section with ID for internal navigation
- Experience: timeline with sticky Education card and achievements/tech badges
- Contact: info cards, social CTA buttons, contact form (non-functional placeholder)
- Footer: profile blurb, quick links, tech list, back-to-top button
Reusable Sections (src/components)
hero.tsx: Stateless typed hero with name, tagline, socials.
about.tsx: Stateless typed about section rendering summary.
experience.tsx: Typed list rendering of experiences with bullets.
projects.tsx: Typed grid of project cards with tech tags and highlights.
skills.tsx: Typed skills section, reuses a private Pill subcomponent.
gallery.tsx: Responsive grid, lazy-loaded native img tags.
nav.tsx: Sticky header with anchor links for sections (#experience, #projects, #skills, #gallery, #about).
footer.tsx: Minimal static footer.
Note: These reusable components are not currently composed in page.tsx; the page renders its own bespoke UI. They can be adopted to centralize structure and reduce duplication.
Configuration
tsconfig.json:
- Next.js TS plugin,
paths aliasing: @/* → src/*.
components.json (shadcn):
- Style preset:
new-york
- RSC enabled (
rsc: true)
- Aliases:
@/components, @/lib, @/components/ui
- Tailwind CSS path:
src/app/globals.css
- Icon library:
lucide
postcss.config.mjs:
- Registers Tailwind via
@tailwindcss/postcss.
next.config.ts:
- Default export with no special config (placeholder).
package.json:
- Scripts:
dev, build, start, lint
Assets and Static Files
public/ contains:
- Icons and SVGs for gallery and theming (
next.svg, vercel.svg, etc.)
- Resume PDF
Ryan_Tran_Resume_Aug2025.pdf referenced in CTA button.
- Images in
page.tsx gallery use static paths (e.g., /asian-male-computer-science-headshot.png). Ensure these exist in public/; otherwise, fallback to /placeholder.svg is used in some places.
Runtime Behavior
- Client-side intervals animate role and gallery changes.
- Buttons navigate via smooth scrolling to anchored sections.
- Social buttons open new tabs or mailto/tel handlers.
- Contact form is UI-only (no submit handler or backend).
- The entire page is a Client Component. Consider moving static sections to Server Components and passing data as props to reduce JS on the client.
- Replace native
<img> tags with next/image for optimized loading, responsive sizing, and automatic lazy-loading.
- Lazy-load below-the-fold sections or split the page into smaller client islands for improved LCP.
- Convert rotating intervals to CSS animations where feasible to reduce re-renders.
- Consider
prefetch={false} and measured external link handling for icons/CTAs.
layout.tsx exports metadata with title, description, and generator.
- Add Open Graph and Twitter metadata, canonical URL, and structured data for richer previews.
Extensibility
- Centralize data sourcing by replacing local arrays in
page.tsx with src/data/profile.ts.
- Compose the stateless section components (
about.tsx, experience.tsx, projects.tsx, skills.tsx, gallery.tsx, footer.tsx) inside page.tsx to avoid duplication and ensure typed contracts.
- Introduce a
src/app/(marketing)/ route group if future pages (blog, case studies) are added.
- Add a server action or API route for the contact form; consider email providers (Resend) or form backends.
Known Gaps
next.config.ts has no image domains or custom headers.
page.tsx references images that may not exist in public/.
- Contact form has no submission handling.
- Dark mode toggle not implemented despite
.dark theme support.
Nav component is defined but not used in page.tsx.
v0, loveable, bolt.new, builder.io, coderabbit, mintlify, framer, claude code, and cursor