How I built this site: a Three.js road you drive by scrolling, pre-rendered for Google
Engineering · October 18, 2026 · 3 min read
The home page is a low-poly countryside where scrolling drives a car past my journey, skills and work. Here is how it stays fast, how a WebGL site still gets indexed, and what the AI chat does with my API key (nothing you can see).
Most developer portfolios are a list. I wanted something that felt like a trip, because the work has been one: school, college, a first job, a university IT team, AI tools, client sites. So the home page is a road through a low-poly countryside, and scrolling drives the car. This is how it works, and what it took to keep it fast and searchable.
The road
The scene is Three.js inside a React 19 app built with Vite. The road is a curve through a hand-placed set of points; buildings for each stop on the journey, container yards for the skill groups and billboards with real screenshots for featured projects are placed beside it. The camera follows the car, and the car's position is just how far you have scrolled. Paper cards float beside the road with the details for the stop you are at. At night (the switch in the header) the lamps come on and a bloom pass makes them glow.
Keeping it fast
- Everything in the countryside is instanced: thousands of trees are one draw call.
- The 3D code is split into its own chunk, so the inner pages never download it.
- Fonts, CSS and images are cached for a year under hashed names; the HTML is always fresh.
- If WebGL is unavailable, the page falls back to a flat backdrop and everything still works.
The first download is about 270 KB gzipped, including React and Three.js.
A 3D site Google can read
Search engines are not great at waiting for WebGL. So at build time the site pre-renders every route to its own HTML file: the home page, projects, each case study, services, the Meerut page, this blog, with real text, titles, descriptions, canonical links and JSON-LD for the person, the services (with prices and areas) and each post. The React app then takes over in the browser and shows the same content with the interactions on top. There is a sitemap with real last-modified dates from git, and every production deploy pings Bing through IndexNow with the pages that changed.
Hosting and deploys
The site runs on Cloudflare Pages. A push to the main branch runs a type check, builds, deploys and submits changed pages to IndexNow, all from GitHub Actions. There is no server to patch.
The chat, and the API key
The chat button in the corner talks to Gemini, but the browser never sees an API key. Requests go to a small serverless function that holds the key, checks the request came from this site, limits how often one visitor can call it, and builds the prompt from the same data file that renders the pages. An earlier version of the site had the key in the frontend, someone scraped it, and that is the day the function was written.
The mobile fixes that mattered
The pinned cards used 100vh for height, which on phones includes the space behind the browser's address bar, so the bottom of each card sat off screen. Switching to the small-viewport unit fixed it in one line. The cards also had scrollbars inside them; now they size to fit, with a fade when there is more.
The notice boards
Inner pages are cork boards with pinned postcards, tickets and index cards, placed with a seeded random so they look hand-arranged but never move between visits. The footer is a postcard home and a highway exit sign, with the little car parking at a flag as you reach the bottom. A hitchhiker with a sign turns up a moment after the page loads with tips, and leaves when you start driving.
What I'd change
Less, sooner. The first version had twice the decoration and half the speed. Cutting things until the road and the cards were the whole idea made it better. The source is on GitHub; the projects it talks about are here.
Tags: Three.js, React, Vite, Cloudflare Pages, SEO, portfolio