Nine practices that ran side by side for decades had to become a site that stays true to his own account, is easy to move through, and can be found in Persian.
The role
Sole designer and developer: information architecture, brand and design language, content model, the Payload and Next.js build with AI assistance, and an in-country stack.
The result
Live since 30 August 2026, with Persian on its own crawlable URLs and bookings open since 12 September; no analytics yet, and that gap is on the record.
Figure 01
Figure 01The Persian site on a phone: the nine practices as ruled rows, the poetry page, and the Shamsi booking calendar.
01Context
A career that resists a single headline
Arash Rezvani is a writer, poet, teacher, photographer and adventurer. By his own account he has published close to ten books and more than a hundred poems, coached cycling for fifteen years, climbed for more than twenty-five and photographed for more than thirty. The brief was a personal site and a journal for all of it.
Persian is his language and his readers’, so Persian is the site’s default, with English beside it. Around his own account I built a publishing site: a page for each part of his work, a shelf of books with a page each, a journal, his songs, search, and a calendar for booking a call. It is edited from a Persian admin and hosted inside Iran.
/en/experience: the English page, set in Latin capitals.
/fa/experience: the same page in Persian, which is never letter-spaced.
Figure 02One page, two URLs: each language is its own document, not a translation switched by a cookie.
02Problem
Truth, structure and reach
The first problem was truth. A life this varied invites rounding up, and the project had already met the worse case: the first content the design was built on described someone else, and his own written account contradicted nearly every fact in it.
The second was structure. His practices have run side by side for decades — writing, teaching riders and instructors, coaching cycling, photography, calligraphy, the mountain, the road, guiding, relief work — and none replaced another. One profile page cannot hold that, and pages that each try to sum it all up end up repeating one another.
The third was reach. For a writer whose books and readers are Persian, the Persian pages have to be findable at their own addresses. A site that renders Persian well but serves it to no crawler has failed at the one thing it is for.
03Constraints
One small server, on a filtered network
The site runs on one server in Iran with two cores and under 4 GB of memory, which also runs its Git server and its mail. From there Docker Hub, Google’s container registry and Quay all answer 403, and YouTube is filtered, so the server cannot reach the videos its own music page lists.
Builds run on the same machine and take both cores for about eight minutes; measured on 2 September, response times went from 5 seconds to 37 and then to a timeout until the build finished. No CI runner was ever installed, so nothing checks a push before it goes live. Each of these shaped a decision further down.
04My role
One designer-developer, one author
I designed and built the site alone, from Arash’s own written account and the messages he sent about each practice. The code started from Payload’s website template, and I built it with AI assistance: Claude is credited on 93 of the 97 commits.
Owned
↳The information architecture: which page holds which part of his account
↳The brand and the written design language
↳The content model: 8 collections, 4 globals and 21 page blocks, 16 of them written for this site
↳The Payload and Next.js build, in two languages
↳The booking system and its calendar
↳The in-country stack, the deploy log and the content-operations log
↳Art direction of the imagery: 37 generated images, the likeness drawn from his photographs
Co-owned
—
Collaborated
↳Arash: his biography, his ten practice statements, his book covers and his photographs
Every figure on the site traces back to his own words; where the site and a draft disagree, the biography wins. Drafts, live preview, scheduling, search indexing and the form builder came with the template; the structure, the rules and the decisions are mine.
05Sources
Nothing inferred, nothing filled in
The site may use two sources for who he is: a document he wrote about himself, and ten messages he sent on 11 September, one per practice. The biography file keeps both, with Persian as the authoritative text, and a table of sourced numbers, each as he stated it, its ≈ or + intact. When an older content file disagreed with his account, the conflict was written into the biography and his account won.
The same file lists what does not exist yet — the rest of his book titles, the text of any poem, his certificates — and the rule for all of it is to render nothing. So the gaps show as plain statements, not placeholders: the books page lists six of close to ten titles and says so, the poetry page explains four forms and says no poem is here yet, and the work page says the calligraphy and photographs are not online.
A placement table in the same file records the page and block where each of his passages now lives, so a later edit cannot quietly put a passage in two places.
The site went public on 30 August; its content became truthful on 3 September, when his account replaced the older file on every page.
“Nothing here is inferred, and no number outside this table may appear on the site.”
Figure 03The books page states its own limits: close to ten titles written, translated and published, six of them listed, the rest not yet.
/fa/poetry: the poems section, waiting for complete poems with their form, never excerpts.
06Structure
One page per job, one home per fact
I gave each page one job. Experience is the overview: nine threads, each linking to the page that carries it in full, and the only band of figures on the site. Teach, Road, Looking and Poetry each hold one part of his account, in his words. Books, the journal and music are collections with an index. Search and Contact serve every page.
What holds it together is the design language’s axiom applied to content: a page says a thing once, and a fact has one home. On the first page audited, Work, the same six things appeared three times — the figures in the hero and again in the stats band, one line as a pull quote and again in a list. Each fact got one owner and the copies were deleted; nothing was rewritten. Across the rollout the audit found fourteen repetitions on five pages, and every fix was a subtraction.
Cross-links replace repetition. Teach and Road each show the book from their own subject, linked to its page, and Teach, Books, Music and Experience close on a band that leads to Contact. Once the inner pages owned their facts, the home page had nothing of its own left to say, so it became the index: the books, six ways in, the country map, the newest songs and the latest notes.
Two older pages have not caught up. Work and Experience predate Road and Looking, and six of Experience’s nine links still point at Work, which now holds only a short list and a ‘not online yet’ band.
01The home page is the index. A bare address goes to Persian, and every route exists under /fa and /en.
02About, and Experience: the overview of all nine threads, with the site’s only band of figures.
03Teach, Road, Looking and Poetry each hold one part of his account.
04Teach and Road link the books from their own subject.
07Approach
One axiom, and every page measured against it
The site first went live on an editorial brand system with a palette drawn from Arash’s own photography: a warm ink, amber, paper, a cool mist and cobalt. Every pair was measured. Amber on paper came out at 2.1:1, so amber is never text on white, and one amber per view became a rule.
Three days later the design language was written down as one sentence, “No line without an end”, with three legal ways for a line to stop: on a wall, on another line, or on a change of ground. Anything else is a defect, not a style, and every page was rebuilt and measured against it.
The rule settled details that usually get argued. The walls sit on the content column’s edge, so every rule a block draws lands on them without the block knowing they exist. A band with its own colour draws no rule, because the colour change is the edge. Every line has one weight, 1.5 px, and no region has a rounded corner. A form field is an underline that shows focus by changing colour, measured at 6.18:1 in the light theme and 7.44:1 in the dark, where 3:1 is required. And Persian is never letter-spaced: every tracking value, Tailwind’s own included, is zeroed for Persian, which had been closing the joins of every Persian headline.
The test for an edge is not whether a boundary exists but whether a reader would see the line stop there. The home portrait failed it by 64 px, so its column now draws a rule of its own.
From a template to a live, bilingual site
01
Build-outPayload’s website template inside Next.js, a Persian admin and a deploy pipeline, from 25 August
02
Brand systemA palette measured from his photography; live on 30 August
01Row rules end on the column rule: a line stops on another line.
02The column rule ends on the band rule under the list.
03The walls stop where paper turns to ink: a change of ground is an edge.
04Every figure in the band comes from the sourced table, its ≈ and + kept.
08Imagery
A likeness drawn from his own photographs
The site launched with an empty media library. His archive held 59 images, and none gave a clean reference: in the first 42 I checked, every close frame had his head under a cap, hat or helmet, usually with sunglasses, and every bare-headed frame showed his face too small to read. So I directed the imagery with an image model, working from those photographs.
The first site images came from two early reference sheets. A week later I built nine wardrobe turnarounds, one for each recurring way he dresses in his photos, each traced to named source photographs and seen from nine fixed angles, and they became the continuity reference for seventeen editorial studies. Every image was briefed on the brand’s rules — its palette, square corners, one amber at most, and no text, logo or invented book title. The eleven site images keep to them; a few of the studies go past the one-amber limit.
Where a real photograph works, it wins: his About portrait and avatar are crops of a photograph of him. Here the generated images are shown as generated; on his own site they carry no label yet, and that is an open item.
Figure 09“Six ways”, the home page’s index of his practices: each practice sits under one of the generated section cards.
09Publishing
Records, and everywhere each one reaches
Books, journal posts and songs are records, not sections of a page, because a record can have its own address, its own metadata and a place in search. Publishing a record does not make it reachable, so each kind is wired to the surfaces a reader or a crawler arrives from.
A book gets a page in each language, a row in the books sitemap and in search, Book structured data that names his role as the cover prints it, and a share image cut from its jacket. A journal post gets a page per language, the posts sitemap, an RSS feed per language, Article structured data and a filter by category; the six categories carry the names of his practices. A song has no page of its own: the music page and the home band read the videos collection, and describe it as a list of VideoObjects.
Around them: canonical URLs, hreflang pairs with x-default on Persian, three sitemaps, a WebSite and Person graph on every page, and share cards: a book uses its own jacket, every other page the site’s card.
The journal does not meet the site’s own rule yet. Twenty of its 21 entries were created in one batch on 10 September, written in his voice, credited to me and dated as far back as January; the other is left from the older content. Until his own writing replaces them, the system works and its content does not.
01One record in the books collection, edited once in the admin.
02A page in Persian and one in English, paired by hreflang.
03A card on the books shelf, and a panel on the page of its subject.
04A row in the books sitemap and in site search, in both languages.
05
/fa/posts: every date in Shamsi, with Persian numerals.
10Key decisions
Six decisions that held
Each was forced by the audience, the source material or the network, not by taste.
01
Give each language its own URL
The first Persian release chose the language by cookie: one URL, two renders. A crawler carries no cookie, so every translated page was reachable at no URL and no hreflang could be emitted. Now English lives under /en and Persian under /fa, and a bare URL goes to the reader’s last language, or to Persian.
Alternatives
One URL, with the language chosen by cookie
Trade-off
Every internal link must carry its prefix, and the language toggle has to be a full page load.
02
Guard bookings with a database index, not a hook
Two confirmed calls can never share a start time. Payload’s own index options cannot say “unique among confirmed rows only”, so the app builds a partial unique index at boot, and public bookings go through one endpoint. Tested as a race: one 201, one 409, one row.
Alternatives
A duplicate check in a save hook
Trade-off
If the index fails to build, the site runs on without the guarantee, so the boot log has to be read.
Evidence
One day’s free quarter-hours in Tehran time; in English the Shamsi date carries its Gregorian date beneath it.
11Solution
From reading to asking
Search reads one index built from pages, posts and books. Since 13 September it reads that index in the page’s own language, so Persian posts come back under Persian titles. Results stop at twelve, and result pages are kept out of search engines, because every query would otherwise become a thin page.
The contact form is a band of underlined fields with a hidden honeypot. The mail server is wired in, and the container refuses to start without its settings, because the failure it guards against is the quiet one: a form that thanks the visitor and tells nobody. The form itself, though, has no notification set in production, so today messages wait in the admin: the same quiet failure, one layer up.
A call is booked in the steps below. Every rule about when — hours, breaks, slot length, the buffer between calls, notice, blocked days, and a switch that closes it — lives in a setting the admin edits, so changing the hours is never a deployment. Bookings send no email yet; the admin opens on the next confirmed calls.
Search is only half fixed. Books and pages still come back under English titles on the Persian search, and the excerpt and category under each result are stored in one language, so the English search shows Persian excerpts and misses matches in English descriptions.
/fa/search for «مربی»: the posts come back under Persian titles; the book still shows its English one.
Booking a call, in five steps
01
Choose a dayA Shamsi month, Saturday first; past days, Fridays and full days are disabled
02
Choose a timeFree quarter-hours in Tehran time, with the visitor’s own time beside them
12Operations
Running it without a release
The admin speaks Persian by default and turns right-to-left on its own; the mark on its login screen is his signature, drawn in place of Payload’s. Drafts, autosave, live preview and scheduled publishing came with the template, but until 8 September scheduling did nothing: the admin queued a job that nothing ran, so a scheduled post would never have appeared. A job runner inside the container now drains the queue every minute.
From 3 September content changes are made at runtime, not in code. Between 11 and 18 September the content-operations log records fifteen writes — his practice statements, the booking settings, the home page’s cards, the music page and its songs — and one rollback: the About page’s nine cards, which broke the Persian page in browsers without WebGL, were taken back through the API within the hour and restored the same day once the code was fixed.
Each write follows the same steps, and each row in the log carries its verification.
AI agents can read the content through MCP but cannot write it; widening that would itself be a code change, made on purpose.
One content change, start to finish
01
ClassifyContent, a setting, something to make editable once, or code
02
Back upThe document in both languages, before the write
13Outcomes
Live, with the gaps named
Live at arashrezvani.me since 30 August 2026 and taking bookings since 12 September. Still open, and on the record: no analytics of any kind, so there are no visitor numbers; no CI runner, so a push reaches the site without lint or type checks; no email from the contact form or from bookings, so both wait in the admin; search that is only half localized; journal entries that are not his; and generated images his site does not label.
Repetitions found and removed
14
The say-it-once audit across five pages; every fix was a subtraction.
Measured outcome · The design language’s rollout audit
Tests passing at the last release gate
307
Beside type checks, lint, a production build and a smoke test before the push.
Measured outcome · Deployment log, 18 September 2026
Persian and English at their own URLs
Persian by default, both halves in hreflang and in the sitemaps.
Delivered output
A deploy log that keeps its failures
18 production runs recorded, five of them failed, beside a separate log of every content write and rollback.
Delivered output
What was delivered
↳Shamsi booking
↳A URL per language
A page per book
14What I learned
What I’d keep, and what bit
01
State the language as one sentence
“No line without an end” settled arguments without another meeting, because every disagreement reduced to one question: does this line arrive somewhere?
02
A bilingual site is not bilingual until each language has a URL
The cookie scheme passed every test a person could run, and failed completely for the one visitor that matters most to a writer’s site: the crawler.
03
An enhancement that cannot fail closed is not one
A WebGL scroll reveal threw in browsers without WebGL and replaced a whole Persian page with an error. It came back behind a capability check and an error boundary that settles to the plain picture.
04
A rule has to reach whatever is added in bulk
Every page was measured against his account one by one, and the journal was not: its entries went in as one batch. It is the one part of the site that fails the site’s own test, and the first thing to replace.
/en/work: none of the calligraphy or photography is online yet, and the page says so.
Figure 04Empty states written as statements: each names what is missing instead of filling the space.
05
Books and the journal: an index and a page for every record; the journal filters by six categories.
06Music reads the videos collection; the home page shows the newest three.
07Search reads pages, posts and books; Contact holds the form and the booking calendar.
08Work predates Road and Looking; its place in the structure is still open.
Figure 05The site as its routes, counted on 27 September 2026. Every route exists twice, under /fa and /en.
Figure 06The home page as an index, further down: the country map that leads to Experience, and the six latest notes. The book covers and song stills between them are his, so they are left out.
03
His own accountThe Lattice written down and every page rebuilt on his biography; truthful from 3 September
04
Practice statementsHis ten messages placed on 11 September, one home each
05
Own URLs and bookingEach language at its own prefix, and a Shamsi calendar for calls, from 12 September
06
Books as pagesA page per book, and search read in the page’s language, 13 September
07
MusicThe channel’s songs as click-to-load cards, 18 September
Figure 07
The experience page in Persian: walls, row rules and a column rule, closed by the ink band of figures.
Figure 08The imagery in place: the live home page opens on the three-window masthead, in English and in Persian, where the whole page mirrors right to left.
Figure 10The same home page on a phone, in Persian: the opening line, the masthead, the six cards two to a row, and the notes.
Figure 11Nine wardrobe turnarounds, one per style he wears: literary artist, relaxed traveller, formal educator, urban creative, heritage motorcyclist, motorcycle coach, adventure tourer, field traveller and relief-service fieldwear.
Figure 12What reached the site: the home masthead and an earlier collage masthead, the six section cards, the closing band (since taken down; its 1200×630 crop is still the share card), and two stand-ins on the About page until a real calligraphy panel is photographed and one of his own photographs arrives.
Figure 13Seventeen editorial studies, each a new composition in one of the nine wardrobes: collage, screen print, hard-light portrait, still life.
Book structured data, with his role as the cover prints it: author, co-author or translator.
06A 1200×630 share image cut from its jacket.
Figure 14One book traced through the site. Posts follow the same pattern and add an RSS feed per language; songs are read by the music page and have no page of their own.
/en/posts: the same index, dated in the Gregorian calendar.
Figure 15The journal index in both languages: six categories named after his practices, twelve entries to a page.
03
Load a video only when it is asked for
Inside Iran a YouTube player needs a VPN. Each song is a click-to-load card whose poster and title come from the site’s own storage, so the card is whole without one and no player loads until the click.
Alternatives
Embed the player on the page
Trade-off
The posters are YouTube’s own frames for now, stored on the site and replaced by hand.
04
Keep the whole stack inside Iran
Docker Hub answers the server with 403: five deployments failed at the base-image pull before it came through an Iranian mirror. Storage, Git, the deploy platform and mail all run inside the country.
Alternatives
Public registries and hosted Git
Trade-off
More to run on one small server: one build ran out of memory and took the Git server down with it.
05
Make each book a record with its own page
A list inside a page block cannot give a book an address, its own metadata or an identity other pages can point to. As a record, a book appears on the shelf, on the page of its subject, in search and at its own URL, so a reader searching «مربی شو» lands on the book. None of the covers states a year or a publisher, so the shelf is ordered by hand, and each card carries the role the cover prints: author, co-author or translator.
Alternatives
The six books as rows inside the books page
Trade-off
No automatic order, and a details section that stays empty until a year or a publisher is known.
06
Treat content as data, not a release
From 3 September the database, not the repository, holds the content. Every change is sorted first: content and settings go through the API, anything that will change again is made editable once, and only schema, behaviour and infrastructure ship as a build. The songs, the music page, the booking hours and the nav all changed without a deployment.
Alternatives
Edit content in the seed files and deploy it
Trade-off
The repository no longer holds the live text, so every write is backed up, diffed and logged by hand.
/en/search for “coach”: English titles, with a Persian excerpt under each.
Figure 16The same search in both languages on a phone, captured on 27 September 2026: what the locale fix solved, and what it did not.
03
DetailsName, phone and email; the phone is checked and a hidden field catches bots
04
ReviewOne screen to confirm before anything is written
05
Booked201 and a receipt; a slot taken meanwhile answers 409, and the calendar offers the rest
Figure 17On a phone in Persian: the chosen day’s free quarter-hours, then the details.
03
WriteEnglish first, then Persian, every row id kept
04
Read backDiff against the backup: only the intended fields may move
05
LogA row in the content-operations log, with its check