www.carecom.de:n uudistus Blazor-sovelluksena tekoälyä hyödyntäen

Lyhyt kokemusraportti ikääntyneen carecom.de-sivuston uudelleentekemisestä. Uudistuksen toteutti suurelta osin – luonnollisesti valvonnassani – Claude Code.

Harald Mühlhoff 5 min lukuaika

Vajaat kymmenen vuotta sitten olin jo kerran rakentanut tämän sivuston kokonaan uudelleen – silloin Reactilla, TypeScriptillä, Reduxilla ja ASP.NET Core -taustajärjestelmällä (vrt. www.carecom.de:n luominen). Stack-konsepti oli vuonna 2017 tuore ja jännittävä, mutta on – kuten kaikki verkkomaailmassa – kerännyt selvää patinaa. Aika siis uudistukselle – kaikella sillä, mitä C# ja .NET vuonna 2026 tarjoavat.

Uusi carecom.de toimii nyt Blazor Web App -sovelluksena .NET 10:llä, palvelinpuolen esirenderöinnillä ja kohdennetusti interaktiivisilla komponenteilla siellä, missä se on järkevää (yhteydenottolomake, evästeilmoitus, kuvakaruselli, mini-Space-Invaders). Ei enää JavaScript-kehystä taustalla, ei Redux-putkea – sen sijaan tyyppiturvallista C#:aa aina UI-komponentteihin asti ja oma, tiivis CSS ilman Bootstrapia.

Tähän asti tavanomaista. Jännittävää ei oikeastaan ole se, mitä siinä lukee, vaan se, kuka sen kirjoitti: reilusti yli 99 % koodista – Razor-komponentit, CSS, palveluluokat, konfiguraatio – tuli Claude Codelta, Anthropicin agenttipohjaiselta CLI-työkalulta. Annoin useissa istunnoissa tavoitteet ja iteraatiot; agentti suunnitteli, kirjoitti, testasi, korjasi ja teki taas ehdotuksia.

Eteneminen

Aloitimme lyhyellä vaatimusten sopimisella – hosting-malli, lokalisointi, UI-kehys. Heti perään Claude haravoi vanhan sivuston sivu sivulta, poimi tekstit sanatarkasti ja tallensi kuvat kansiorakenteineen paikallisesti. Useiden iteraatioiden myötä siitä syntyi täydellinen klooni, joka on lähellä alkuperäistä sekä ulkoasultaan että sisällöltään – ja sai myöhempien istuntojen kuluessa omia parannuksiaan.

Komponentit ja asettelukuori

Arkkitehtuurissa painopiste oli uudelleenkäytettävissä komponenteissa: SiteHeader, SiteFooter, PageHeaderRow, LanguageSwitcher, RightSidebar, PersonCard, BlogTeaser, ReferenceList, QuoteBlock, Testimonial, PhotoSlider, TextCarousel, CookieConsent, SeoTags, SocialIcons, LatestPosts, SpaceInvaders – aina kun sama kaava esiintyi useassa paikassa, se tiivistettiin omaksi Razor-komponentikseen.

Kaikkien sisältösivujen päällä on SitePage-kuori nimetyillä paikoilla: BeforeMain täysleveille elementeille ennen containeria (etusivun karuselli, Photography-kuvakaruselli), ChildContent pääpalstana, Sidebar automaattisen alavalikon valinnaisena laajennuksena ja Aside täytenä korvaajana vakiosivupalkille (yhteystietosivu kahdella PersonCardilla). Jokainen sivu perii PageBasen, joka pitää Lang/Effective-rutiinin keskitetysti ja säästää näin sivukohtaisen toistokoodin kokonaan.

Monikielisyys – deklaratiivisesti

Lokalisointi URL-etuliitteellä (/de/…, /en/…) ja kielenvaihtimella SiteHeaderin oikeassa yläkulmassa. Aidosti monikieliset tekstit (sama DOM-muoto, eri sanat) ovat tyyppiturvallisina L-recordeina keskitetyssä SiteContent.cs-tiedostossa, jotka lähdegeneraattori luo osiokohtaisista XML-tiedostoista. SiteContent.Contact.Phone[Effective] palauttaa oikean variantin – ilman IStringLocalizer-painolastia ja ilman .resx-generointia.

Markupin rakenteellisille kielihaaroille – kokonaisille vain saksankielisille osioille, kuten Consulting-sivun IT-konsultointilohkolle, tai kielikohtaisille eri DOM-muodoille – on käytettävissä pieniä Razor-komponentteja: <De>, <En> ja <Fi>. Ne lukevat aktiivisen kieliarvon [CascadingParameter(Name = "Lang")]-määreellä MainLayout-cascadesta, jota puolestaan syötetään suoraan URL-polusta. Tämä korvaa kymmenet @if (Effective == "de") -lohkot deklaratiivisella markupilla, joka lukeutuu kuin HTML.

Photography

Photography-sivulla pyörii 42 kuvan karuselli, joka vie koko selaimen leveyden – mutta kiinteällä 3:2-kuvasuhteella, joka sopii alkuperäisiin 1920×1280-kuviin, ja object-fit: contain -asetuksella. Tavallisilla näytöillä jokainen kuva näkyy täydellisesti sovitettuna; erittäin suurilla näytöillä max-height: 70vh rajaa korkeuden, ja kapea letterbox-reunus sivuilla on tietoisesti hyväksytty kompromissi pystysuuntaista rajausta vastaan. Kuvatekstit ja navigointinuolet tulevat näkyviin vain hiiren ollessa päällä, jotta kuva olisi pääosassa. Kosketuslaitteilla, joilla ei ole hover-ominaisuutta, ohjaimet pysyvät jatkuvasti näkyvissä.

Brändianimaatio: caredef koreografoituna SVG:nä

Faviconia ei suunniteltu käsin, vaan se luotiin suoraan sanamerkistä: Claude määritti pikselitiheysprofiilin avulla carecom.png:n ensimmäisen „C“-kirjaimen rajaavan suorakulmion, otti näytteen hallitsevasta väristä (#10AE5B – CARECOMin vihreä) ja renderöi siitä 32×32-, 64×64- ja 192×192-kokoiset faviconit.

caredef.svg – mielleyhtymiä CARECOM-tuotemerkin ympäriltä

Vanha caredef.jpg brändisivulla (CARECOM-tavaramerkki) on nyttemmin käännetty kokonaan animoiduksi SVG:ksi: CARECOM®-sanamerkki base64-upotettuna PNG:nä (pikselintarkka kirjainmuoto, koska mikään asennettu fontti ei toista sitä), seitsemän satelliittikäsitettä (Competence, Commitment, Communication, Computer, Complete, Competitive, Common Sense) ja foneettinen kuvateksti aitoina SVG-tekstielementteinä alkuperäisillä paikoillaan. Avattaessa logo jousahtaa keskelle (jousitettu easing ylityksellä), sitten seitsemän sanaa häivähtävät esiin peräkkäin 150 ms:n tahtiin hieman ulompaa siirretyistä kohdista paikoilleen, ja lopuksi kuvateksti lentää alhaalta ylös. prefers-reduced-motion -asetusta kunnioitetaan.

Pääsiäismuna: Space Invaders 6502-perinteessä

Informatiker-/Computer Scientist -sivulla pyysin Claudea integroimaan mini-Space-Invaders-pelin – kunnianosoituksena ensimmäiselle 6502-assembly-projektilleni Apple ][:lla. Tuloksena on oma canvas-pelinpätkä puhtaalla selain-JavaScriptilla, sivuston värimaailmassa (aksenttisiniset invaderit footerin mustalla, porrastettuna pistearvon mukaan vaaleasta tummaan) ja kaksikielisillä canvas-teksteillä.

Sinne johtaa kolme reittiä: „6502“-ankkuri ohjelmointikielilistan leipätekstissä, oma /de/play-reitti jaettavaa suoraa linkkiä varten sekä klassinen Konami-koodi (↑ ↑ ↓ ↓ ← → ← → B A), joka navigoi peliin miltä tahansa sivulta.

CSS-eristys

Komponenttikohtainen CSS on johdonmukaisesti eriytetty: jokaista Foo.razor-tiedostoa kohden on Foo.razor.css, jonka Blazor varustaa yksilöllisellä [b-xxxxx]-scope-merkinnällä. Aiemmin 860 rivin pituinen site.css on kutistunut vajaaseen 150 riviin – design-tokenit, perustypografia ja aidot läpileikkaavat kaavat. Niissä harvoissa kohdissa, joissa sääntöjen on ylitettävä komponenttirajoja (SiteFooterin ankkurivärit sisäkkäisille SocialIcons-ankkureille, <li>-reunus RightSidebar-paikassa, leveyssääntö ContactFormin InputText-elementin renderöimälle syötekentälle), ::deep hoitaa homman.

Tietosuoja ja analytiikka

Google Analytics 4 on rakennettu aitona opt-in-ratkaisuna: ilman napsautusta „Hyväksy“ evästebannerissa gtag.js-tiedostoa ei koskaan ladata, evästeitä ei aseteta eikä tietoja lähetetä Googlelle. Vasta nimenomaisen suostumuksen jälkeen (DSGVO:n 6 artiklan 1 kohdan a alakohta) käynnistys alkaa, ja GoogleAnalyticsTracker-komponentti laukaisee jokaisella Blazorin sisäisellä navigoinnilla lisä-page_view-tapahtuman – muuten SPA-reitinvaihdoissa analytiikkaan päätyisivät vain alkuperäiset sivulataukset. Tietosuojaseloste kuvaa mekanismin omassa osiossaan peruutusohjeineen. SeoTags-komponentti asettaa sivukohtaisesti otsikon, kuvauksen, canonicalin, OpenGraph- ja Twitter-Card-tagit sekä hreflang-vaihtoehdot.

Renderöintitilat ja sudenkuopat

Tärkeä tekninen oppitunti, joka tuli vastaan peräti kahdesti: CascadingValue staattisesti renderöidystä vanhemmasta ei ylitä renderöintitilan rajaa @rendermode InteractiveServer -alipuuhun automaattisesti. Seuraus: yhteydenottolomakkeessa nimiöt ilmestyivät interaktiivisen siirtymän jälkeen yhtäkkiä saksaksi, vaikka sivu oli osoitteessa /en/contact; ja Space-Invaders-minipelissä introlohko näytti itsepintaisesti DE-varianttia, koska interaktiivisen komponentin sisällä olevat <De>/<En>-kuluttajat näkivät vain oletusarvon. Siisti korjaus molemmissa tapauksissa: Lang eksplisiittisenä [Parameter]-arvona sisään Page-Razorista, ja komponentti julkaisee sen sisäisesti uudelleen lapsilleen <CascadingValue Value="@Lang" Name="Lang" IsFixed="true">-elementilla.

Yhteenveto

Se mikä ennen olisi vienyt viikkoja käsityötä – sisällön migraatio, komponenttien pilkkominen, pikselintarkkuus alkuperäiseen nähden, tietosuojatekstit, sähköpostin asettelu, kuvakaruselli, faviconit, lokalisointi, animoitu brändi-SVG, minipeli kolmella löydettävyyspolulla, täydellinen CSS-eristys – syntyi hallittavissa olevassa määrässä istuntoja Claude Coden kanssa. Minun osuuteni oli olennaisesti tavoitteenasettelu, katselmointi ja satunnainen kurssinkorjaus; agentti hoiti loput: suunnittelun, kirjoittamisen, buildaamisen, savutestauksen, dokumentoinnin.

Jos pohdit vastaavaa uudistusta tai agenttipohjaisia koodityökaluja omassa yrityksessäsi: ota minuun rohkeasti yhteyttä!

Harald Mühlhoff

Tapahtui virhe. Lataa uudelleen 🗙

Yhdistetään uudelleen palvelimeen …

Uudelleenyhdistäminen epäonnistui – seuraava yritys sekunnin kuluttua.

Uudelleenyhdistäminen epäonnistui.
Yritä uudelleen tai lataa sivu uudelleen.

Palvelin keskeytti istunnon.

Istunnon jatkaminen epäonnistui.
Yritä uudelleen tai lataa sivu uudelleen.