I rebuilt my portfolio while the old site still served production traffic.
That sounds safer than a direct replacement. It also creates two competing goals.
I wanted the new site public quickly. I also wanted every acceptance check complete.
Treating both goals as one gate delayed useful feedback. Splitting them changed the release.
The first failure was planning everything as one launch
The new site already had the important product surfaces.
It had the portfolio, writing index, article routes, RSS feed, sitemap, redirects, and Sanity reads.
The complete acceptance list was much larger.
It covered four widths, three theme modes, keyboard use, text zoom, reduced motion, accessibility, and production checks.
Every check mattered. They did not all need to block the first private preview.
My first plan treated the whole list as one release gate.
That plan increased waiting time before anyone could inspect the deployed result.
I changed the decision. The preview gate would cover only failures that could make the preview unsafe or unusable.
The first gate became small and explicit:
- Protect private information.
- Run linting, type checking, unit tests, and the production build.
- Fix only failures that block a safe preview.
- Commit the exact preview state.
- Deploy that commit to the existing Vercel project.
The full acceptance work continued after the preview existed.
This was not permission to ignore quality. It changed when each check had to pass.
A PDF became the most important release blocker
The public resume looked like a static asset.
It also contained a private phone number.
The HTML pages did not expose that number. The downloadable PDF still did.
That made the preview unsafe before any visual defect did.
I replaced the file with a phone-free public build from the approved resume source.
I then checked the public asset instead of trusting the page copy.
The lesson was simple. Privacy checks must include downloads, generated files, metadata, and copied assets.
My next release checklist now starts with public artifacts. It does not start with screenshots.
One failed test compared spelling, not behavior
The first Playwright run passed 21 of 22 tests.
The remaining test compared two equivalent root URLs with different trailing slashes.
The browser reached the correct page. The assertion still failed.
I could have changed the application to satisfy the string.
That would have changed working behavior to repair a weak test.
I changed the assertion instead. It now normalizes the root URL before comparison.
This distinction matters in browser tests.
A useful assertion protects user behavior. A brittle assertion protects one serialization of that behavior.
My next attempt asks a direct question: what failure would a user observe?
If the answer is none, the assertion probably needs work.
The content existed, but the page hid part of it
Sanity had seven published articles. Every article had approved cover data.
The article page did not render those covers.
The content query and adapter worked. The visible article remained incomplete.
I added the image fields to the adapter and rendered the covers through next/image.
I also approved cdn.sanity.io as the remote image host.
The fix crossed several layers because the defect crossed several layers.
Schema data alone does not create a feature. The query, adapter, configuration, component, and browser must agree.
I added a browser check for the final image instead of stopping at the query result.
Publishing needed an end-to-end path
The site could read published Sanity articles. It could not refresh them after a publish event.
Without revalidation, a correct editor action could leave a stale public page.
I added a signed revalidation route for article events.
The route checks the request signature before it invalidates article caches.
An unsigned request returns 401. A correctly signed request returns 200.
I then created the production webhook for article create, update, and delete events.
Draft events stay excluded. The verification did not change any Sanity document.
This failure changed my definition of a content feature.
The feature starts at the editor action. It ends when the reader receives the updated page.
The next run tests that whole path without publishing test documents.
I promoted the tested artifact
The verified preview used commit e993985.
I promoted that exact preview to production.
I did not create a second build between verification and promotion.
Vercel deployment metadata confirmed the Git commit and the original preview deployment.
The previous production deployment remained available during verification.
That rollback path changed the cutover from a hopeful replacement into a controlled change.
Production checks covered the home page, writing index, a representative article, RSS, sitemap, and resume.
They also covered the legacy redirects, Sanity reads, and both signed and unsigned webhook requests.
The custom domain expired before I configured its DNS record.
I deferred that domain and kept the Vercel production URL as the canonical public URL.
Honest placeholders made the second release easier
The first release used labelled project placeholders where approved media did not exist.
Those placeholders were incomplete, but they did not pretend to show a product.
After launch, I inspected the public repositories for Spine, Kāinga, and Cyclops.
Spine had no suitable interface capture. I used a factual ingestion architecture diagram.
Kāinga had a real chat interface in its repository. I captured that interface with mocked authentication only.
Cyclops already had a repository image showing pod rows and their View Logs controls.
Each asset now has source, license, contribution, and optimization records in the portfolio repository.
The placeholder rule prevented a quick visual improvement from becoming a false claim.
My next attempt keeps the placeholder whenever repository evidence cannot support a final image.
What I keep for the next release
I now separate three release questions.
First, is the preview safe enough for review?
Second, did the exact preview pass the complete production gate?
Third, can I reverse the production change while verification continues?
Each question has different evidence and a different deadline.
The preview needs privacy, a working build, and the primary routes.
Production needs the complete acceptance result and a verified deployment identity.
The cutover needs rollback, production checks, and honest status reporting.
This release still had failures. The useful part was changing the next decision after each one.
That is the release process I will use for another run.