This project represents a complete rewrite of my portfolio website, moving from a heavy Next.js/React stack to vanilla JavaScript with TypeScript and Vite. It resulted in dramatically faster build times and improved page performance.
My portfolio is a relatively simple website that doesn't require the complexity of a full React framework. The overhead of Next.js was slowing down both development and production builds without providing substantial benefits for my use case.
- Before (Next.js): 65 seconds
- After (Vite + Vanilla JS): 8 seconds
- Result: 88% faster builds
- Before (Next.js): ~110 kB per page + 102 kB shared chunks
- After (Vite + Vanilla JS): 37 kB gzipped total
- Result: 66% smaller payload to users
- Before: 270.68 MB
- After: 50.62 MB
- Result: 81% smaller cache
- No middleware overhead (eliminated 33.9 kB)
- No hydration overhead
- Faster initial page load
- Single optimized bundle
- Improved Lighthouse scores
- Next.js 15
- React 19
- Node.js server-side rendering
- Complex build pipeline
- Vite - Lightning-fast build tool
- TypeScript - Type safety without React overhead
- Vanilla JavaScript - No framework dependencies
- Custom Client-Side Router - Lightweight SPA routing
- Web Components - For reusable UI elements
Built a lightweight router from scratch that handles:
- History API navigation
- Dynamic route matching
- 404 handling
- Link interception for smooth SPA navigation
const router = new Router("app");
router
.addRoute("/", () => createPage(homeHtml))
.addRoute("/projects", () => createPage(projectsHtml))
.setNotFound(() => createPage("<h1>404 - Not Found</h1>"))
.start();Created custom elements for reusable components:
<experience-card><skill-card><education-card><project-card>
- Lazy loading images with Intersection Observer
- Code splitting
- Modern image formats (WebP, AVIF)
- Minimal JavaScript footprint
src/
├── components/ # Web components
├── navigation/ # Custom router
│ └── router.ts
├── templates/ # Page templates
├── views/ # HTML content
│ ├── home.html
│ └── projects.html
├── main.ts # Entry point
└── style.css # Global styles
For Vercel deployment, a simple vercel.json handles SPA routing:
{
"rewrites": [
{
"source": "/(.*)",
"destination": "/index.html"
}
]
}- Faster builds
- Better performance
- Simpler mental model
- Complete control over routing
- Smaller bundle size
- Server-side rendering (didn't need it)
- Built-in routing (built my own)
- React ecosystem (didn't need it for a portfolio)
# Install dependencies
pnpm install
# Development server
pnpm dev
# Production build
pnpm build
# Preview production build
pnpm previewThis migration proved that choosing the right tool for the job matters. For a portfolio website, vanilla JavaScript with modern tooling (Vite, TypeScript, Web Components) provides all the benefits of a modern development experience without the overhead of a full framework.
The best code is sometimes less code.
Live Site: timothywhite.dev
Build Time: 8 seconds
Bundle Size: Significantly reduced
Lighthouse Score: AWESOME