<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Alexandru Draghici</title>
    <description>The latest articles on Forem by Alexandru Draghici (@alexandru_draghici_3c127b).</description>
    <link>https://forem.com/alexandru_draghici_3c127b</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3790015%2Fb0ea8cc5-2802-4d9c-932d-b997dec04725.jpg</url>
      <title>Forem: Alexandru Draghici</title>
      <link>https://forem.com/alexandru_draghici_3c127b</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/alexandru_draghici_3c127b"/>
    <language>en</language>
    <item>
      <title>Event-Driven Architecture pentru platformă auto în RO</title>
      <dc:creator>Alexandru Draghici</dc:creator>
      <pubDate>Wed, 25 Feb 2026 10:52:55 +0000</pubDate>
      <link>https://forem.com/alexandru_draghici_3c127b/event-driven-architecture-pentru-platforma-auto-in-ro-14o8</link>
      <guid>https://forem.com/alexandru_draghici_3c127b/event-driven-architecture-pentru-platforma-auto-in-ro-14o8</guid>
      <description>&lt;h1&gt;
  
  
  Event-Driven Architecture: listări auto, dealeri și leasing
&lt;/h1&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;O &lt;strong&gt;event-driven architecture&lt;/strong&gt; te ajută să decuplezi fluxuri precum listări, verificare dealeri, leasing și notificări.&lt;/li&gt;
&lt;li&gt;Cheia e consistența: folosește &lt;strong&gt;Transactional Outbox&lt;/strong&gt;, &lt;strong&gt;idempotency&lt;/strong&gt; și un contract de eveniment versionat.&lt;/li&gt;
&lt;li&gt;Observability (tracing + metrics) și o schemă (Avro/JSON Schema) reduc debug-ul în producție.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  De ce o platformă auto ajunge rapid la complexitate
&lt;/h2&gt;

&lt;p&gt;Construiești o platformă ca auto101.ro: listări pentru mașini noi și rulate, dealeri verificați, opțiuni de leasing, consultanță gratuită și „livrare la cheie”. Inițial pare CRUD: adaugi ofertă, o publici, o vezi în listă.&lt;/p&gt;

&lt;p&gt;Apoi apar cerințele care îți rup monolitul:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;publicare listare ⇒ indexare în search, calcul preț „corect”, audit, notificări&lt;/li&gt;
&lt;li&gt;dealer verificat ⇒ badge, reguli de eligibilitate, scoring, blocare automată&lt;/li&gt;
&lt;li&gt;cerere leasing ⇒ pre-calificare, documente, status updates, timeline pentru client&lt;/li&gt;
&lt;li&gt;comparații între opțiuni ⇒ agregări, caching, invalidări&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Când fiecare acțiune „scrie în 5 tabele și mai cheamă 3 servicii”, începi să simți de ce &lt;strong&gt;event-driven architecture&lt;/strong&gt; e atractivă: fiecare domeniu reacționează la evenimente, nu la call-uri în lanț.&lt;/p&gt;

&lt;h2&gt;
  
  
  O soluție pragmatică: event-driven architecture cu un „spine” de evenimente
&lt;/h2&gt;

&lt;p&gt;Nu ai nevoie de microservicii peste tot din ziua 1. Dar un backbone de evenimente îți permite să scalezi pe măsură ce crește produsul.&lt;/p&gt;

&lt;h3&gt;
  
  
  Domenii și bounded contexts
&lt;/h3&gt;

&lt;p&gt;Un mod util de a împărți:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Listings&lt;/strong&gt;: listare, preț, status, media&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dealers&lt;/strong&gt;: KYC/validare, rating, comisioane&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leasing&lt;/strong&gt;: lead, ofertare, status, documente&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search/Discovery&lt;/strong&gt;: index, ranking, sugestii&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notifications&lt;/strong&gt;: email/SMS/push, template-uri, preferințe&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Într-o &lt;strong&gt;event-driven architecture&lt;/strong&gt;, fiecare context publică evenimente „de fapt” (facts): &lt;code&gt;ListingPublished&lt;/code&gt;, &lt;code&gt;DealerVerified&lt;/code&gt;, &lt;code&gt;LeasingLeadCreated&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contractul de eveniment: schema, versiuni și idempotency
&lt;/h2&gt;

&lt;p&gt;Un contract bun îți economisește luni de suport.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recomandări de payload
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;include &lt;code&gt;event_id&lt;/code&gt; (UUID) și &lt;code&gt;occurred_at&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;include &lt;code&gt;aggregate_id&lt;/code&gt; + &lt;code&gt;aggregate_type&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;include &lt;code&gt;schema_version&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;include &lt;code&gt;trace_id&lt;/code&gt; pentru debugging distribuit&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exemplu JSON (simplificat):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"event_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"c7f3d5c6-2a8f-4f3b-9d4b-1c2a4a8c9f3e"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ListingPublished"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"occurred_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-02-25T10:23:41.120Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aggregate_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"listing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aggregate_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"listing_12345"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"trace_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dealer_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dealer_77"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"price"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12990&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EUR"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"car"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"make"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Volkswagen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Golf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"year"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2017&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"fuel"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"diesel"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Idempotency la consumatori
&lt;/h3&gt;

&lt;p&gt;Orice consumer trebuie să fie pregătit pentru duplicate (retries, replays). Model simplu:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tabel &lt;code&gt;processed_events(event_id, processed_at)&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;dacă &lt;code&gt;event_id&lt;/code&gt; există, skip&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementare: Transactional Outbox (fără „dual write”)
&lt;/h2&gt;

&lt;p&gt;Greșeala clasică: scrii în DB și publici în broker separat. Dacă publicarea eșuează, ai date fără eveniment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Transactional Outbox&lt;/strong&gt; rezolvă: în aceeași tranzacție salvezi și schimbarea, și un record în &lt;code&gt;outbox&lt;/code&gt;. Un worker publică ulterior.&lt;/p&gt;

&lt;h3&gt;
  
  
  Schema minimală
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;listings&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;dealer_id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;price_cents&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;outbox&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;event_type&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;aggregate_id&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="n"&gt;JSONB&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;outbox_unpublished_idx&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;outbox&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;published_at&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Publicare listare + outbox (pseudo TypeScript)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./db&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;v4&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;uuid&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;publishListing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;listingId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;listing&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;one&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`UPDATE listings
       SET status = 'PUBLISHED', updated_at = now()
       WHERE id = $1
       RETURNING id, dealer_id, price_cents`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;listingId&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ListingPublished&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;schema_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;occurred_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;aggregate_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;listing&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;aggregate_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;dealer_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dealer_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;price_cents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;listing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;price_cents&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;none&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`INSERT INTO outbox(id, event_type, aggregate_id, payload)
       VALUES($1, $2, $3, $4::jsonb)`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;listing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worker-ul de outbox poate publica în Kafka/RabbitMQ/SNS. Pentru Kafka, vezi documentația oficială: &lt;a href="https://kafka.apache.org/documentation/" rel="noopener noreferrer"&gt;https://kafka.apache.org/documentation/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Flux concret: de la listare publicată la „transparență” pentru utilizator
&lt;/h2&gt;

&lt;p&gt;Un flux realist într-o event-driven architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Listings&lt;/strong&gt; emite &lt;code&gt;ListingPublished&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search&lt;/strong&gt; consumă și indexează (Elastic/OpenSearch)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pricing/Insights&lt;/strong&gt; calculează un „fair price band” (min/median/max) și emite &lt;code&gt;ListingPriced&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Notifications&lt;/strong&gt; trimite alertă pentru utilizatori care urmăresc „Golf 2017 diesel sub 13k”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit/Compliance&lt;/strong&gt; persistă un log imuabil (util pentru dispute)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Avantaj: dacă notificările sunt în mentenanță, listarea tot se publică. Când revii, refaci consumul din offset.&lt;/p&gt;

&lt;h2&gt;
  
  
  Observability: tracing și metrici pentru evenimente
&lt;/h2&gt;

&lt;p&gt;În producție, întrebarea nu e „a mers?”, ci „unde s-a blocat?”.&lt;/p&gt;

&lt;p&gt;Checklist pentru event-driven architecture:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;trace_id&lt;/strong&gt; propagat din request → eveniment → consumer&lt;/li&gt;
&lt;li&gt;metrici pe consumer: lag, retry rate, DLQ rate&lt;/li&gt;
&lt;li&gt;loguri structurale cu &lt;code&gt;event_id&lt;/code&gt; și &lt;code&gt;aggregate_id&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OpenTelemetry e un standard solid pentru tracing distribuit: &lt;a href="https://opentelemetry.io/" rel="noopener noreferrer"&gt;https://opentelemetry.io/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern-uri utile: DLQ, retries, versioning
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Retries cu backoff + Dead Letter Queue
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;retry pentru erori tranzitorii (timeout, rate-limit)&lt;/li&gt;
&lt;li&gt;DLQ pentru payload invalid sau bug logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pseudo-config (conceptual):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;consumer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;maxAttempts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;
    &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exponential&lt;/span&gt;
  &lt;span class="na"&gt;dlq&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;topic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;events.dlq&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Versioning fără să rupi consumatorii
&lt;/h3&gt;

&lt;p&gt;Reguli simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;adaugi câmpuri noi ca opționale&lt;/li&gt;
&lt;li&gt;nu schimbi semantica unui câmp existent&lt;/li&gt;
&lt;li&gt;când e nevoie, crești &lt;code&gt;schema_version&lt;/code&gt; și păstrezi compatibilitate o perioadă&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I learned / Gotchas (din implementări reale)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;„Exact once” e rar realist end-to-end; tratează sistemul ca &lt;strong&gt;at-least-once&lt;/strong&gt; și fă consumatori idempotent.&lt;/li&gt;
&lt;li&gt;Outbox-ul devine un hotspot dacă nu cureți: pune retention (ex. șterge publicate &amp;gt; 7 zile) și monitorizează.&lt;/li&gt;
&lt;li&gt;Evenimentele prea „grase” (payload enorm cu zeci de câmpuri) cresc costurile și fragilitatea; preferă payload-uri orientate pe use-case.&lt;/li&gt;
&lt;li&gt;Dacă ai un proces de „dealer verificat”, evită să pui PII în evenimente; trimite referințe și fă lookup securizat.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cum aș aplica asta pe AUTO101 (pași concreți)
&lt;/h2&gt;

&lt;p&gt;Dacă construiești auto101.ro sau ceva similar, o adoptare incrementală a event-driven architecture ar arăta așa:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Introdu outbox&lt;/strong&gt; în serviciul de listări (cel mai mare ROI)&lt;/li&gt;
&lt;li&gt;Creează 2 consumatori separați: &lt;strong&gt;Search indexer&lt;/strong&gt; și &lt;strong&gt;Notifications&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Adaugă idempotency + DLQ înainte să crești numărul de evenimente&lt;/li&gt;
&lt;li&gt;Instrumentează tracing cu OpenTelemetry și dashboard pentru lag&lt;/li&gt;
&lt;li&gt;Abia apoi extrage domenii (Leasing, Dealers) în servicii separate dacă ai presiune de scaling/ownership&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Un CTA util: dacă ai deja un monolit și vrei să introduci evenimente fără rescriere totală, începe cu un singur flux (ex. &lt;code&gt;ListingPublished&lt;/code&gt;) și măsoară impactul (timp de publicare, erori, lag). Apoi extinde contractul.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally about &lt;a href="https://auto101.ro" rel="noopener noreferrer"&gt;event-driven architecture&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>backend</category>
      <category>kafka</category>
      <category>microservices</category>
    </item>
    <item>
      <title>Real-time AI translation earbuds: architecture deep dive</title>
      <dc:creator>Alexandru Draghici</dc:creator>
      <pubDate>Wed, 25 Feb 2026 10:12:56 +0000</pubDate>
      <link>https://forem.com/alexandru_draghici_3c127b/real-time-ai-translation-earbuds-architecture-deep-dive-4knc</link>
      <guid>https://forem.com/alexandru_draghici_3c127b/real-time-ai-translation-earbuds-architecture-deep-dive-4knc</guid>
      <description>&lt;h1&gt;
  
  
  Real-time AI translation earbuds: architecture deep dive
&lt;/h1&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;A real-time translator is a streaming system: &lt;strong&gt;VAD → ASR → MT → TTS → playback&lt;/strong&gt;, glued together with backpressure and a strict latency budget.&lt;/li&gt;
&lt;li&gt;The UX lives or dies by two numbers: &lt;strong&gt;time-to-first-translation (TTFT)&lt;/strong&gt; and &lt;strong&gt;end-to-end latency&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;You’ll want &lt;strong&gt;session state&lt;/strong&gt;, &lt;strong&gt;incremental (partial) results&lt;/strong&gt;, and &lt;strong&gt;graceful degradation&lt;/strong&gt; when network quality drops.&lt;/li&gt;
&lt;li&gt;This post uses “real-time AI translation earbuds” as the reference feature, with implementation patterns that map well to products like &lt;strong&gt;Echolink România (echolinkhub-ro.com)&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Problem framing: what “real-time” really means
&lt;/h2&gt;

&lt;p&gt;When people say &lt;em&gt;real-time AI translation earbuds&lt;/em&gt;, they usually expect a conversational experience: minimal delay, natural turn-taking, and audio that doesn’t feel “robotic” or out of sync.&lt;/p&gt;

&lt;p&gt;From a systems point of view, you’re building a low-latency, multimodal streaming pipeline with these constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Latency budget&lt;/strong&gt;: target ~300–800ms TTFT for short phrases; longer utterances will exceed that but should still stream partials.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jitter tolerance&lt;/strong&gt;: mobile networks introduce unpredictable RTT and packet loss.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Battery and thermals&lt;/strong&gt;: earbuds + phone can’t run heavy models continuously at full throttle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy and compliance&lt;/strong&gt;: audio is sensitive; data retention and transport security matter.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if your product is “just” a digital mediation service (like Echolink’s onboarding/intermediere model), the engineering behind real-time AI translation earbuds is the same: you’re orchestrating services and devices so translation feels immediate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution overview: a streaming pipeline with clear contracts
&lt;/h2&gt;

&lt;p&gt;A robust architecture is typically split into two planes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Control plane&lt;/strong&gt;: auth, session creation, language configuration, device pairing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data plane&lt;/strong&gt;: audio frames and streaming inference results.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s the canonical data plane:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Capture&lt;/strong&gt;: microphone audio frames (e.g., 20ms PCM @ 16kHz)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VAD&lt;/strong&gt; (voice activity detection): reduces cost and improves segmentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ASR&lt;/strong&gt; (automatic speech recognition): streaming partial transcripts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MT&lt;/strong&gt; (machine translation): incremental translation, with sentence boundary heuristics&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TTS&lt;/strong&gt; (text-to-speech): streaming synthesis for playback in the earbuds&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In practice, “real-time” happens only if each stage is designed for &lt;strong&gt;incremental outputs&lt;/strong&gt; and &lt;strong&gt;backpressure&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation details: protocols, buffers, and backpressure
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Choosing the transport: WebRTC vs WebSocket
&lt;/h3&gt;

&lt;p&gt;For real-time AI translation earbuds, you’ll usually pick between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WebRTC&lt;/strong&gt;: best for low-latency audio, built-in jitter buffer, congestion control.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket&lt;/strong&gt;: simpler to implement, good enough for many use cases, but you must manage buffering and packet timing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a production-grade audio path on mobile, WebRTC is hard to beat. The official WebRTC project docs are a good starting point: &lt;a href="https://webrtc.org/" rel="noopener noreferrer"&gt;https://webrtc.org/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you go with WebSocket, keep messages small (frames) and include sequencing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Audio framing and a minimal message schema
&lt;/h3&gt;

&lt;p&gt;A common mistake is shipping whole-second audio blobs. You want predictable, small frames for smooth streaming.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"audio_frame"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"seq"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1842&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"codec"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pcm_s16le"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sample_rate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"channels"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"timestamp_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;53210&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"payload_b64"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For performance, prefer binary frames (not base64) in real systems, but the contract above makes the flow explicit.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backpressure: don’t let one slow stage sink the whole app
&lt;/h3&gt;

&lt;p&gt;Streaming pipelines fail when a downstream stage (say TTS) slows down and upstream keeps pushing data.&lt;/p&gt;

&lt;p&gt;Use a bounded queue per stage and apply one of these policies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Drop&lt;/strong&gt;: discard old partials when newer partials exist (good for UI text updates)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Coalesce&lt;/strong&gt;: merge partial updates into the latest state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spill&lt;/strong&gt;: persist to disk (rarely worth it for live translation)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simple async pipeline (Node.js-ish pseudocode):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BoundedQueue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="na"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;max&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// drop oldest&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shift&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;asrOut&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BoundedQueue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mtOut&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BoundedQueue&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;onAsrPartial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;asrOut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;setInterval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;latest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;asrOut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;translated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;translateIncremental&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;mtOut&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;translated&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This “drop oldest” strategy is surprisingly effective for real-time AI translation earbuds because the user mostly cares about &lt;em&gt;the latest&lt;/em&gt; hypothesis.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture decisions: on-device vs cloud inference
&lt;/h2&gt;

&lt;p&gt;You’ll see three common deployments:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cloud-first&lt;/strong&gt;: ASR/MT/TTS in cloud; phone streams audio.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid&lt;/strong&gt;: VAD + some ASR on-device; MT/TTS in cloud.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-device&lt;/strong&gt;: everything on phone (rare for high-quality multilingual with 144 languages).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For a broad language matrix (e.g., 144 languages) and consistent quality, cloud inference is typically the pragmatic choice. But you should still consider on-device components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On-device VAD reduces bandwidth and cost.&lt;/li&gt;
&lt;li&gt;On-device language ID can auto-select source language.&lt;/li&gt;
&lt;li&gt;On-device caching for common phrases can cut TTFT.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A useful reference for streaming ASR patterns and constraints is the Whisper project (even if you don’t use it directly): &lt;a href="https://github.com/openai/whisper" rel="noopener noreferrer"&gt;https://github.com/openai/whisper&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Handling multilingual sessions (144 languages) without a config nightmare
&lt;/h2&gt;

&lt;p&gt;Supporting many languages isn’t just “more models.” It’s routing, fallbacks, and UX defaults.&lt;/p&gt;

&lt;h3&gt;
  
  
  Session model
&lt;/h3&gt;

&lt;p&gt;Represent a session with explicit language intent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"session_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"s_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source_lang"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ro"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"target_lang"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"conversation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"profanity_filter"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"off"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"punctuation"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For real-time AI translation earbuds, “conversation” mode usually means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;two-way turn taking&lt;/li&gt;
&lt;li&gt;speaker diarization (optional)&lt;/li&gt;
&lt;li&gt;automatic end-of-utterance detection&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Routing rules
&lt;/h3&gt;

&lt;p&gt;Use a routing table that selects an MT engine by pair, not just by target:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ro→en might use Engine A&lt;/li&gt;
&lt;li&gt;ja→ro might use Engine B&lt;/li&gt;
&lt;li&gt;fallback: pivot through en if direct pair quality is low&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pivoting adds latency, so treat it as a fallback, not the default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Practical use cases and how the pipeline behaves
&lt;/h2&gt;

&lt;p&gt;Here are three concrete scenarios where real-time AI translation earbuds shine, and what to optimize:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Travel check-in&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optimize: TTFT and TTS naturalness&lt;/li&gt;
&lt;li&gt;Trick: pre-warm TTS voice at session start to avoid first-synthesis lag&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Business meeting&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optimize: terminology consistency&lt;/li&gt;
&lt;li&gt;Trick: allow a per-session glossary injected into MT (even a small key-value list helps)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Customer support on the go&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optimize: resilience under weak networks&lt;/li&gt;
&lt;li&gt;Trick: degrade to text-only translation when audio RTT spikes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Gotchas (things that bite in production)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1) Partial transcripts will “change their mind”
&lt;/h3&gt;

&lt;p&gt;Streaming ASR emits hypotheses that get revised. If you translate every partial naïvely, users hear corrections mid-sentence.&lt;/p&gt;

&lt;p&gt;Mitigations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Translate only when punctuation confidence is high&lt;/li&gt;
&lt;li&gt;Use a “stability threshold” (e.g., last N tokens unchanged)&lt;/li&gt;
&lt;li&gt;Synthesize audio in chunks and avoid replaying already spoken content&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2) Acoustic echo cancels your own TTS (or doesn’t)
&lt;/h3&gt;

&lt;p&gt;If the earbuds leak audio back into the mic, ASR can transcribe the TTS output.&lt;/p&gt;

&lt;p&gt;Mitigations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enable echo cancellation (AEC) on the capture device&lt;/li&gt;
&lt;li&gt;tag TTS playback timestamps and suppress ASR during playback windows&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3) Latency spikes from model cold starts
&lt;/h3&gt;

&lt;p&gt;If you spin up inference workers on demand, your first request is slow.&lt;/p&gt;

&lt;p&gt;Mitigations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;keep a warm pool per region&lt;/li&gt;
&lt;li&gt;pre-initialize the most common language pairs&lt;/li&gt;
&lt;li&gt;cache speaker embeddings/voices for TTS if applicable&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I learned building for “translation in the ear” UX
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The best metric isn’t average latency; it’s &lt;strong&gt;p95 TTFT&lt;/strong&gt;. Users remember the worst delays.&lt;/li&gt;
&lt;li&gt;“Accurate but late” loses to “slightly imperfect but timely” in live conversation.&lt;/li&gt;
&lt;li&gt;Real-time AI translation earbuds need &lt;strong&gt;stateful sessions&lt;/strong&gt;. Stateless request/response translation feels brittle.&lt;/li&gt;
&lt;li&gt;Shipping a “144 languages” claim is easy; delivering consistent pair quality requires ruthless monitoring and fallbacks.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References and deeper reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;WebRTC official docs (transport and real-time media concepts): &lt;a href="https://webrtc.org/" rel="noopener noreferrer"&gt;https://webrtc.org/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Whisper (ASR research/implementation reference and constraints): &lt;a href="https://github.com/openai/whisper" rel="noopener noreferrer"&gt;https://github.com/openai/whisper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;For language codes and interoperability (BCP 47 overview): &lt;a href="https://www.rfc-editor.org/rfc/bcp/bcp47.txt" rel="noopener noreferrer"&gt;https://www.rfc-editor.org/rfc/bcp/bcp47.txt&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Helpful next step (non-salesy CTA)
&lt;/h2&gt;

&lt;p&gt;If you’re experimenting with real-time AI translation earbuds (or evaluating a product like Echolink România at &lt;a href="https://echolinkhub-ro.com" rel="noopener noreferrer"&gt;https://echolinkhub-ro.com&lt;/a&gt;), sketch your pipeline and write down your target &lt;strong&gt;TTFT&lt;/strong&gt;, &lt;strong&gt;p95 latency&lt;/strong&gt;, and &lt;strong&gt;fallback modes&lt;/strong&gt; (text-only, lower sample rate, or delayed full-sentence translation). If you want, share your numbers and constraints in a Dev.to comment—I can suggest where to shave milliseconds without wrecking quality.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally about &lt;a href="https://echolinkhub-ro.com" rel="noopener noreferrer"&gt;real-time AI translation earbuds&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webrtc</category>
      <category>audioprocessing</category>
      <category>ai</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Arhitectură pentru un site de prezentare în 3 zile</title>
      <dc:creator>Alexandru Draghici</dc:creator>
      <pubDate>Wed, 25 Feb 2026 09:46:45 +0000</pubDate>
      <link>https://forem.com/alexandru_draghici_3c127b/arhitectura-pentru-un-site-de-prezentare-in-3-zile-1j3o</link>
      <guid>https://forem.com/alexandru_draghici_3c127b/arhitectura-pentru-un-site-de-prezentare-in-3-zile-1j3o</guid>
      <description>&lt;h1&gt;
  
  
  Arhitectură pentru un site de prezentare livrabil în 3 zile
&lt;/h1&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Un &lt;strong&gt;site de prezentare&lt;/strong&gt; livrat rapid cere standardizare: design system, componente reutilizabile și un model clar de conținut.&lt;/li&gt;
&lt;li&gt;Static-first (SSG) + CDN + formulare serverless oferă performanță, costuri mici și mentenanță redusă.&lt;/li&gt;
&lt;li&gt;SEO local nu e „un plugin”: e un set de decizii (schema, NAP, pagini de locație, sitemap, redirects) plus verificări automate.&lt;/li&gt;
&lt;li&gt;Emailul pe domeniu are gotchas reale (SPF/DKIM/DMARC). Automatizează-le sau vei pierde timp în suport.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Problema: viteză de livrare fără compromisuri
&lt;/h2&gt;

&lt;p&gt;Când promiți „gata în 3 zile”, riscul nu e doar să scrii cod repede. Riscul e să nu poți repeta procesul pentru următorul client. Un &lt;strong&gt;site de prezentare&lt;/strong&gt; pentru servicii locale (cabinete, saloane, instalatori) are aceleași cerințe de bază:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pagini: Acasă, Servicii, Despre, Contact (+ uneori Portofoliu/Prețuri)&lt;/li&gt;
&lt;li&gt;mobile-first, scor bun în Core Web Vitals&lt;/li&gt;
&lt;li&gt;formulare de contact funcționale + protecție anti-spam&lt;/li&gt;
&lt;li&gt;SEO local: NAP consistent (Name/Address/Phone), map embed, schema, pagini indexabile&lt;/li&gt;
&lt;li&gt;email profesional: &lt;code&gt;contact@domeniu.ro&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dacă tratezi fiecare proiect ca un „one-off”, vei ajunge să rezolvi aceleași bug-uri de 10 ori. Abordarea care scalează este să proiectezi un template tehnic și un pipeline de configurare.&lt;/p&gt;

&lt;h2&gt;
  
  
  Soluția: un blueprint repetabil (SSG + config + checklist)
&lt;/h2&gt;

&lt;p&gt;Pentru un &lt;strong&gt;site de prezentare&lt;/strong&gt; livrat rapid, modelul care funcționează cel mai des este:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Static-first&lt;/strong&gt;: Next.js (SSG) / Astro / Eleventy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conținut ca date&lt;/strong&gt;: JSON/YAML/MDX, validat cu un schema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment automat&lt;/strong&gt;: build → preview → production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observabilitate minimă&lt;/strong&gt;: uptime + logs pentru formulare.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;De ce static-first? Pentru că majoritatea site-urilor de prezentare sunt read-mostly. Dacă nu ai nevoie de autentificare sau conținut dinamic complex, SSR permanent e un cost operațional inutil.&lt;/p&gt;

&lt;p&gt;Referințe utile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web.dev despre Core Web Vitals: &lt;a href="https://web.dev/vitals/" rel="noopener noreferrer"&gt;https://web.dev/vitals/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Documentația Next.js (SSG / routing / metadata): &lt;a href="https://nextjs.org/docs" rel="noopener noreferrer"&gt;https://nextjs.org/docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Implementare: model de conținut tipizat + componente
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Un singur fișier de configurare (care reduce discuțiile)
&lt;/h3&gt;

&lt;p&gt;În loc să „hardcodezi” date în componente, folosește un config central. Într-un &lt;strong&gt;site de prezentare&lt;/strong&gt;, 80% din schimbări sunt: nume firmă, servicii, program, adresă, telefon, linkuri.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// site.config.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SiteConfigSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;logoUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;contact&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;email&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;lat&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;lng&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="na"&gt;businessHours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;opens&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;closes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;seo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ro_RO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SiteConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;SiteConfigSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;De ce Zod? Pentru că validezi input-ul înainte să ajungă în build. Un &lt;strong&gt;site de prezentare&lt;/strong&gt; cu o adresă lipsă nu e un bug „vizual”; e o problemă de conversie.&lt;/p&gt;

&lt;h3&gt;
  
  
  Componente atomic + layout-uri fixe
&lt;/h3&gt;

&lt;p&gt;Ține un set mic de layout-uri:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;MarketingLayout&lt;/code&gt; (hero + secțiuni)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ServiceLayout&lt;/code&gt; (beneficii + CTA + FAQ opțional)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ContactLayout&lt;/code&gt; (map + form + NAP)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apoi construiești secțiuni reutilizabile:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Hero&lt;/code&gt;, &lt;code&gt;TrustBar&lt;/code&gt;, &lt;code&gt;ServiceGrid&lt;/code&gt;, &lt;code&gt;Testimonial&lt;/code&gt;, &lt;code&gt;FAQ&lt;/code&gt;, &lt;code&gt;Footer&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cheia pentru livrare rapidă: nu oferi „design nelimitat”. Oferă variante controlate (ex: 3 stiluri de hero, 2 variante de card). Într-un &lt;strong&gt;site de prezentare&lt;/strong&gt;, consistența bate creativitatea ad-hoc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pipeline: preview links, checks și livrare fără stres
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Build + deploy cu verificări obligatorii
&lt;/h3&gt;

&lt;p&gt;Un pipeline minimal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;lint&lt;/code&gt; + &lt;code&gt;typecheck&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;unit&lt;/code&gt; pentru helpers (slugify, sitemap generation)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;build&lt;/code&gt; + &lt;code&gt;bundle analysis&lt;/code&gt; (opțional)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lighthouse-ci&lt;/code&gt; pe pagini cheie
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .github/workflows/ci.yml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-node@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;node-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run lint&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run typecheck&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run test&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Un &lt;strong&gt;site de prezentare&lt;/strong&gt; nu are voie să „pice” la deploy pentru un typo în config. Automatizarea e diferența dintre 3 zile și 7.&lt;/p&gt;

&lt;h3&gt;
  
  
  Preview environment pentru feedback rapid
&lt;/h3&gt;

&lt;p&gt;Folosește preview deployments (Vercel/Netlify/Cloudflare Pages). Clientul vede schimbările fără să atingi producția. Pentru un &lt;strong&gt;site de prezentare&lt;/strong&gt;, asta reduce ping-pong-ul pe capturi de ecran.&lt;/p&gt;

&lt;h2&gt;
  
  
  SEO local: lucruri tehnice care chiar contează
&lt;/h2&gt;

&lt;p&gt;SEO local e ușor de subestimat. În realitate, pentru un &lt;strong&gt;site de prezentare&lt;/strong&gt; local, micile detalii sunt multiplicatori.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Schema.org pentru LocalBusiness
&lt;/h3&gt;

&lt;p&gt;Google citește mai bine entitatea business-ului dacă ai structured data.&lt;/p&gt;

&lt;p&gt;Referință: &lt;a href="https://schema.org/LocalBusiness" rel="noopener noreferrer"&gt;https://schema.org/LocalBusiness&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://schema.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;LocalBusiness&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Nume Firmă&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;telephone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;+40...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;address&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;PostalAddress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;streetAddress&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Str...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;addressLocality&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Oraș&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;addressCountry&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;RO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;geo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GeoCoordinates&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;latitude&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;44.43&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;longitude&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;26.10&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;url&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://exemplu.ro&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2) NAP consistent + pagină de contact „completă”
&lt;/h3&gt;

&lt;p&gt;Pe un &lt;strong&gt;site de prezentare&lt;/strong&gt;, pagina Contact nu e o formalitate. Include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;telefon click-to-call&lt;/li&gt;
&lt;li&gt;adresă copy-friendly&lt;/li&gt;
&lt;li&gt;program&lt;/li&gt;
&lt;li&gt;Google Maps embed&lt;/li&gt;
&lt;li&gt;link către profilul Google Business (dacă există)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3) Sitemap + robots + redirects
&lt;/h3&gt;

&lt;p&gt;Checklist tehnic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;sitemap.xml&lt;/code&gt; generat la build&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;robots.txt&lt;/code&gt; fără blocări accidentale&lt;/li&gt;
&lt;li&gt;redirect 301 pentru &lt;code&gt;www&lt;/code&gt; vs non-&lt;code&gt;www&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;canonical setat corect&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Email profesional: SPF/DKIM/DMARC (gotchas reale)
&lt;/h2&gt;

&lt;p&gt;Mulți cred că „email pe domeniu” înseamnă doar creare inbox. În practică, livrabilitatea e o problemă. Pentru un &lt;strong&gt;site de prezentare&lt;/strong&gt; care colectează lead-uri, emailul trebuie să ajungă în inbox, nu în spam.&lt;/p&gt;

&lt;p&gt;Minimul recomandat:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SPF: autorizează serverele care trimit&lt;/li&gt;
&lt;li&gt;DKIM: semnează mesajele&lt;/li&gt;
&lt;li&gt;DMARC: policy + rapoarte&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Documentație bună (Google): &lt;a href="https://support.google.com/a/answer/33786" rel="noopener noreferrer"&gt;https://support.google.com/a/answer/33786&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; dacă folosești un provider pentru formulare (ex: trimiți notificări), SPF/DKIM trebuie să includă și acel provider. Altfel, lead-urile devin „fantome”.&lt;/p&gt;

&lt;h2&gt;
  
  
  Formulare: serverless + anti-spam fără fricțiune
&lt;/h2&gt;

&lt;p&gt;Pentru un &lt;strong&gt;site de prezentare&lt;/strong&gt; static, ai câteva opțiuni:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Netlify Forms / Formspark / Formspree&lt;/li&gt;
&lt;li&gt;endpoint serverless (Cloudflare Workers / Vercel Functions)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Un endpoint minimal ar trebui să aibă:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;rate limiting&lt;/li&gt;
&lt;li&gt;honeypot field&lt;/li&gt;
&lt;li&gt;validation (Zod)&lt;/li&gt;
&lt;li&gt;log + alert pe erori
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pseudo: serverless handler&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;honeypotFilled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;rateLimitOk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ContactSchema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;safeParse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;success&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendMail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parsed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I learned (și ce aș face diferit)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Standardizarea câștigă: un &lt;strong&gt;site de prezentare&lt;/strong&gt; livrat rapid are nevoie de „opinii” în cod (decizii implicite), altfel fiecare proiect devine negociere.&lt;/li&gt;
&lt;li&gt;Validează conținutul ca pe input de API. Greșelile de copy sunt inevitabile; build-ul trebuie să le prindă.&lt;/li&gt;
&lt;li&gt;SEO local e un set de fișiere și reguli, nu un task de „final”. Când îl pui în checklist de build, nu-l uiți.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Gotchas: lucruri care îți pot strica deadline-ul
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Fonturi: prea multe variante → payload mare → Lighthouse scade.&lt;/li&gt;
&lt;li&gt;Imagini: fără &lt;code&gt;srcset&lt;/code&gt;/optimizare → LCP slab. Folosește pipeline de optimizare.&lt;/li&gt;
&lt;li&gt;Canonical greșit (mai ales în preview) → indexare ciudată.&lt;/li&gt;
&lt;li&gt;DNS pentru email: propagare + configurări incomplete → suport neplanificat.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Un exemplu practic: cum aș structura livrarea în 3 zile
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ziua 1&lt;/strong&gt;: colectare date (servicii, poze, NAP), completare config, alegere variantă de template.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ziua 2&lt;/strong&gt;: implementare pagini + optimizări (imagini, schema, sitemap), preview pentru feedback.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ziua 3&lt;/strong&gt;: DNS, email (SPF/DKIM/DMARC), redirecturi, verificări Lighthouse, push production.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pentru un &lt;strong&gt;site de prezentare&lt;/strong&gt;, această secvență funcționează pentru că reduce dependențele: design-ul e controlat, iar riscurile (DNS/email) sunt împinse spre final, cu checklist clar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Call-to-action (util, nu promo)
&lt;/h2&gt;

&lt;p&gt;Dacă vrei să-ți simplifici propriul proces, începe prin a-ți crea un „starter” intern: un template + un &lt;code&gt;site.config.ts&lt;/code&gt; tipizat + un checklist de DNS/SEO. Iar dacă ai nevoie de un exemplu real de pachet „site complet în 3 zile” (hosting, email și SEO local incluse), poți analiza oferta și mesajele de produs de la &lt;a href="https://vreau-site.ro" rel="noopener noreferrer"&gt;https://vreau-site.ro&lt;/a&gt; ca studiu de poziționare și scope.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally about &lt;a href="https://vreau-site.ro" rel="noopener noreferrer"&gt;site de prezentare&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>seo</category>
      <category>architecture</category>
      <category>nextjs</category>
    </item>
  </channel>
</rss>
