React SPA s nezametno slomannym SEO-sloem za 36 časov i tri produkšen-reliza pereehal na self-hosted Next.js. Claude provël audit i revju, ChatGPT Codex napisal vse 7 803 stroki koda, a moj vklad svëlsja k šesti rešenijam i triždy skazannomu slovu «vperëd».
U menja nebolšoj muzykalny sajt. React 19, Vite, klijentskij routing; iznačal’no on byl sgenerirovan v Lovable, zatem eksportirovan na GitHub i perenesën na moj VPS. Utrom 10 ijulja ja poprosil AI provesti nezavisimoju arhitekturnuju revju. K večeru 11 ijulja sajt uže rabotal v produkšene na Next.js: 45 kanoničeskich URL v Google, sobstvennaja analitika s zapis’ju v Postgres, odnošagovye redirekty so vsech starych adresov i tri rollback-fajla, kotorye tak i ne ponadobilis’.
Ja ne pisal kod. I ne pisal plan, priëmočnye testy, konfigi Caddy ili procedury pereključenija. Vsë eto sdelali dve AI-sistemy, proverjaja rabotu drug druga i poroj dokazivaja, što drugaja ošiblas’. Moj rol’ sokratilas’ do «čeloveka, kotoryj otvečaet za neobratimy rešenija».
Dejstvujusčie lica
Ja. Prinimal rešenija o brende — kakoj URL-slug polučit EP, što delat so splash-straniczej, — odobral pereključenija produkšena i platil za server.
Claude — rukovoditel’. Provël pervyj audit, sostavil plan migracii, vydaval zadaniya i nezavisimo proverjal zjavlenija ispolnitela na živom produkšene. Kod prilojenija on ne pisal.
ChatGPT Codex — razrabotchik. Realizoval vsë: prilojenie na Next.js, reestr maršrutov, priëmočnye nabory, izmenenija Docker i Caddy, staging-okruženiâ. Dvadcat’ sem’ kommitov, 292 otsleživaemyh fajla, 7 803 fizičeskie stroki ishodnogo koda.
Takoe razdelenie okazalos’ važnee, čem ja očekival. Ne potomu, što odnomu iz AI nel’zja doveryat v čelovečeskom smysle, a potomu, što každy zamečal to, što propustil drugoj. Pričëm v obe storony.
Audit Claude našël to, čevo mesjacami ne videl nikto
Znany problem: jednostranične aplikacije dajut praznu HTML-oboločku, zato crawleri bez JavaScript nič ne vidut. Ja očekivah, što audit to potvrdi i predloži ispravlenja.
No on zagruzil strani v pravom brauzeru, počakal završene React i pročital DOM:
document.title
// "Generic Site Title" <- odnakovy na vseh stranah
document.querySelectorAll('link[rel=canonical]').length
// 0
Za metadannye stranic site ispolzoval react-helmet-async. V React 19 ta biblioteka prestaje primenjati head-tegi i ne vydaje ošibku. Kontent každoj stranicy otobražalsja normalno, no title, canonical i strukturirovannye dannye nikuda ne popadali. Edinstvenny vidimy simptom byla neizmenna podpiska vkladki brauzera — a nikto ne razglądaet vkladki sobstvennogo sajta. Měsiacy code review nič ne našli, potomu što kod byl pravilnym; umrela biblioteka pod nim.
Audit takže izučil aktualnu dokumentaciju Google i obnaružil fakt, ubivši dejstvennoje rešenie: prerender-proksi s raspoznavanjem botov, k kotoromu ja by pribegnul, Google oficialno priznal ustarevšim. A važny dlja menja AI-crawlery — GRTBot, ClaudeBot i PerplexityBot — voobšče ne ispolnjajut JavaScript. Vtorogo šansa pokazati metadannye net. Znacit, polna migracija.
Kako vygljadit zadańje ot odnogo AI drugomu
Claude razbil migraciju na relizy i vydaval Codex zadańja s žestkimi granicami. Samym interesnym byli ne zadači, a ogrančenija:
- Definition of done do načala koda. Pjat proverok na stranicu. Pervaja: metadannye i osnovnoj kontent dolžny prisutstvovat v ishodnom HTML, što proverjaetsja čerez curl bez
JavaScript. Za stranicy pesen eto označalo polny tekst, proverenny postročno. Poze Codex nazval eto samym poleznym ukazaniem: imenno ono ne pozvolilo polučit «Next.js tolko po nazvaniju» s klientskoj zagruzkoj tekstov i statěj.
-
Reestr maršrutov kak edinstvenny istočnik pravdy. Odin tipizirovanny massiv vseh kanoničeskih URL. Iz nego strojatsja sitemap, robots.txt, llms.txt, karta redirektov i priëmočnye testy. Stary sitemap prevratilsja v vymysel imenno potomu, što ego podderživali ručno.
-
Sčëčik pariteta, blokirovavšij pereključenie. Každy maršrut načinal so statusa
pendingi dolžen byl zaslužitparity. Instrumenty Codex ne pozvoljali vypuskat prodakšen, poka sčëčik ne pokazal 25/25. V rannyh otčëtah bylo: «6 scaffold, 19 pending, 0 parity. Pereključenie nevozmožno». -
Granicy skupa. Dlja každogo reliza bylo perečislěno, čevo nelzja kasat’sja. Inache nabravšij temp agent načnët ulučšat to, što vy ne prosili.
Etalon, kandidat, raznica
Dlja vizualnoj proverki Codex sdelal trëhpanelnye sravnenija každoj stranicy: živoj prodakšen sleva, kandidat na Next.js v centre i piksel’naja raznica sprava — dlja desktopa i širiny 375 px. Za ves’ projekt v repozitorii popalo 101 takoje izobraženie. Listat’ stenu počti čërnyh diff-panelej — bližajšij analog detektora lži dlja etoj migracii: ljuboje smeščenie maketa svetitsja konturom, s kotorym ne posporiš.
Funkcional’naja čast’ byla ešče strože. HTTP acceptance-skript soderžit 72 statičeskih proverki. Na 45 maršrutah, s QR-parametrami, metadannymi, JSON-LD, OG-izobraženijami i privatnymi poverhnostjami, oni razvorachivajutsja v 1 505 vypolnennyh proverok na každy prodakšen-reliz.
Moi ljubimy dvenadcat’ strok projekta prevratajut arhitekturnoe pravilo v uslovie, sposobnoje uronit’ sborku. U každogo redirekta dolžen byt’ rovno odin vladelec: libo reverse proxy, libo frejmvork — nikogda oba:
const forbiddenCaddyTokens = [
"redir /about ",
"redir /home ",
"redir /blog ",
"path_regexp legacyPath",
"redir /legacy-page ",
"redir /old-campaign ",
];
for (const token of forbiddenCaddyTokens) {
assert.equal(caddy.includes(token), false,
`Path redirect still has two owners: ${token.trim()}`);
}
assert.equal(caddy.includes("/legacy-resolver/"), false,
"Caddy must not own this route after the Next resolver ships");
Redirekt, opredelënny v dvuh mestah, odnazhdy obâzatel’no načnët protivorečit sam sebe. Teper’ ob ètom govori CI.
Gde ošibal sâždy AI — oficial’no
Ošibki Codex byli processnymi. Triždy on sohranjal otčëty s dokazatel’stvami v /tmp, kotory isčezajet posle perezagruzki, hotâ emu veleli kommitit’ ih. Četyre otčëta podrâd soderžali pustye sekcii skrinšotov i odnovremenno utverždali, što vizual’ny paritet proverën. Avt-testy on zapustil na prodakšen-baze, potomu čto imenno tam byl dostupny API. Každuju problemu ispravili, prevrativ trebovanie v merge blocker; v pozdnih otčëtah Codex perečislâl vse sozdannye im QA-zapisi v prodakšene s ih ID.
Ošibki Claude byli ošibkami uverennosti. On polučil production sitemap iz ustarevšego keša i objavil uže razvërnutoe ispravlenie otsutstvujuščim: Codex predâvjal hëši kontenta i vyigral spor. Claude zâvil, čto backend-repozitorij suščestvuet, potomu čto na nego ukazyval git remote, no repozitorij nikogda ne sozdavali. Bag rassylki on pripisal feature-vetke Codex; Codex dokazal, čto vetka ne kasalas’ etogo koda, i našël nastojaščuju pričinu: Drizzle zavernul ošibku PostgreSQL o dublirujuščemsâ ključe v error.cause, gde obrabotčik eë ne videl.
Kogda Codex sprosili, s čem on byl ne soglasen, no promolčal, otvet okazalsâ neozidannym. On scital pixel diff každoj juridičeskoj stranicy i logina izbytočnym — i byl prav. Ne veril, čto odin progon Lighthouse dolžen byt’ žestkim gejtom — tože prav, hotâ gejt našël real’nye problemy prefetch i zagruzki izobraženij. I scital llms.txt eksperimental’noj gigienoj, a ne dokazannym faktorom ranžirovanija — snova prav, no pri cene v odin sgenerirovanny fajl sdelat’ ego vsë ravno stolo. Moj vyvod: ispolnitel’, kotoryj molča vypolnjaet somnitel’nye dlâ nego instrukcii, — defekt processa, a ne dostoinstvo. Sprašivajte o somnenijah.
Hudšij moment my sozdali sami
Ne DNS i ne otladčiki social preview. Pri podgotovke Phase 1C nativnye Docker-sborki nakopili promežutočnye sloi, poka disk VPS ne zapolnilsâ na 100%. PostgreSQL ne smog zapisat’ checkpoint i vošël v cikl vosstanovlenija, a production API nedolgo načal otvečat’ 500. Staging-upražnenie vyzvalo nebol’šoj, no nastojaščij prodakšen-incident.
Otvet Codex byl obrazcovym: ostanovit’ gotovjaščuju migraciju i pereključenie Caddy, udalit’ tol’ko visjaščie build-sloi, dat’ Postgres samostojatel’no zavershit’ WAL recovery bez perezapuska kontejnera i bez kasanija fajlov dannyh, zatem zanovo vsë proverit’. Svobodnoe mesto vyroslo s nulâ do 15,79 GB, v operacionny plan dobavili eženedel’nuju očistku obrazov, a u každoj buduŝej sborki pojavilos’ predvaritel’noe uslovie: 15 GB svobodnogo mesta do zagruzki obrazov. Incident zanâl dvadcat’ minut i porodíl samoe poleznoje ekspluatacionnoje pravilo projekta.
Čisla
| Metrika | Do | Posle |
|---|---|---|
| Maršruty s unikálnymi metadannymi v ishodnom HTML | 0 | 45 |
| Samyj bol’šoj JS pri pervoj zagruzke | 1,3 MB | 658 KB |
| Mobile LCP stranicy pesni | ne izmerjalsâ | 1,8 s |
| Neizvestnye puti, vozvraščajuščie nastojaščij 404 | 0% | 100% |
| Real’no suščestvujûŝie URL v sitemap | 9 iz 15 | 45 iz 45 |
| Vypolnennye acceptance-proverki na reliz | 0 | 1 505 |
| Zakommichennye visual-diff izobraženija | 0 | 101 | | Pereključeniâ prodakšena / potrebovavšiesâ otkaty | | 3 / 0 | | Napisannye mnoj stroki koda | 0 | 0 |
Po git timestamps bloki realizacii zanimali ot 25 do 55 minut, staging acceptance — okolo 90 minut, a každo okno pereključeniâ prodakšena — primerno 15 minut. Samo pereključenie trafika zanimalo 302 millisekundy. Ves’ put’ ot pervogo audita do tret’ego prodakšen-reliza uložilsâ v 36 časov.
Čto na samom dele sdelal ja
Šest rešenij: podtverdil celevuju arhitekturu, vybral URL-slug dlâ EP, rešil sud’bu splash-stranicy, opredelil podhod k analitike, prinyal kompromiss po kontentnomu dolgu i razrešil tri pereključeniâ prodakšena. Každo bylo biznes-rešenijem ili razrešenijem na neobratimoe dejstvie. Ni odno ne trebovalo otkryt’ redaktor.
Teper’ ja gotov zaštiŝat’ takoje razdelenie truda: AI otvečajut za obratimuju rabotu i proverku, ljudi — za neobratimy rešenija i standarty. Standarty — tože nastojaŝaja rabota, i prišli oni so storony rev’ju: definition of done, granicy skupa, pravilo hranit’ artefakty v git, otrepetirovannye otkaty. Bez etogo sloja ja, verojatno, polučil by bystruju migraciju s tichimi probelami, a ne bystruju migraciju s 1 505 dokazatel’stvami.
Esli zahotite povtorit’: dajte agentu-ispolnitelju reestr, iz kotorogo vsë vyvoditsja, vmesto nabora fajlov dlâ ručnoj sinkhronizacii; zastav’te podtverždat’ zajaavlenija zakommicennymi artefaktami; razrešite emu samomu blokirovat’ pereključenie; sprosite, s čem on vtajne ne soglasen; i ostav’te odno slovo za soboj.
Mojim bylo «vperëd».