De ce am migrat de la React la Blade + Alpine
Pana de curand, anonym93.dev era un React SPA clasic: Vite + React 18 + React Router v6 + TanStack Query + shadcn/ui. Toata partea publica (articole, portofoliu, pagini de prezentare) era randata client-side dupa ce browser-ul descarca si executa un bundle de ~500KB JavaScript.
Motivatia pentru migrare a venit din trei directii:
- SEO slab: Google indexa partial paginile — continutul era vizibil doar dupa hydration
- Core Web Vitals slabe: LCP peste 3 secunde pe mobile, INP peste 300ms
- Complexitate inutila: aveam React + TanStack Query doar ca sa fetch-uim continut static din CMS
Solutia: Blade (Laravel) + Alpine.js pentru interactivitate minima. Vite ramane doar pentru CSS (Tailwind v4). Zero JavaScript de aplicatie.
Inainte vs dupa (numerele reale)
Bundle size
- Inainte: ~500KB JS + 68KB CSS = 568KB critical assets
- Dupa: 68KB CSS + ~15KB Alpine.js (CDN, cached) = 83KB
- Reducere: 85%
Core Web Vitals (mobile, 4G simulat)
- LCP: 3.2s → 0.9s
- INP: 310ms → 45ms
- CLS: 0.12 → 0.02
- TTFB: 180ms → 250ms (SSR costa putin, dar merita)
Indexare Google
- Inainte: ~40% pagini indexate corect (continut vizibil pentru crawler)
- Dupa: 100% pagini indexate, continut complet in HTML initial
Planul de migrare
Migrarea a durat aproximativ 2 saptamani lucrate part-time. Pasii principali:
- Inventariere pagini React si componente
- Creare layout Blade + componente base (navbar, footer, hero)
- Rescriere pagina cu pagina (home, articles, catalog, detail pages)
- Migrare interactivitate catre Alpine.js (mobile menu, locale switcher, theme toggle)
- Eliminare dependente React si package.json cleanup
- Update teste si CI
Pasul 1: De la componente React la componente Blade
Exemplu concret — un card de articol. Inainte in React:
// resources/js/src/components/ArticleCard.tsxexport function ArticleCard({ article }: { article: Article }) {
return (
<a href=/articles/${article.slug}} className="group rounded-xl border p-6">
<h3 className="text-lg font-bold">{article.title}</h3>
<p className="text-sm text-muted">{article.excerpt}</p>
</a>
);
}
Dupa, in Blade:
{{-- resources/views/components/article-card.blade.php --}}@props(['article'])
<a href="/articles/{{ $article['slug'] }}" class="group rounded-xl border p-6">
<h3 class="text-lg font-bold">{{ $article['translation']['title'] }}</h3>
<p class="text-sm text-muted">{{ $article['translation']['excerpt'] }}</p>
</a>
Apelul in pagina: <x-article-card :article="$article" />. Simplu, declarativ, randat server-side — zero JS.
Pasul 2: Interactivitatea — cand apelezi Alpine.js
Alpine.js acopera 90% din cazurile de interactivitate pe site-uri de prezentare: meniu mobile, dropdown-uri, modal-uri, tabs, toggle de tema, form-uri simple.
Exemplu — mobile menu toggle. Inainte avea nevoie de React state:
const [open, setOpen] = useState(false);return (
<button onClick={() => setOpen(!open)}>Menu</button>
{open && <nav>...</nav>}
);
Dupa, in Alpine (o linie de cod):
<div x-data="{ open: false }"> <button @click="open = !open">Menu</button>
<nav x-show="open" x-transition>...</nav>
</div>
Alpine se incarca de pe CDN (14KB minified gzipped), fara build step, fara hydration.
Pasul 3: Fetching de date — de la TanStack Query la PHP Controller
Inainte, fiecare pagina facea fetch catre API:
const { data: articles } = useQuery({ queryKey: ['articles'],
queryFn: () => fetch('/api/cms/content?kind=ARTICLE').then(r => r.json()),
});
Dupa, in controller Laravel:
public function articles(Request $request){
$articles = ContentItem::query()
->where('kind', 'ARTICLE')
->where('status', 'PUBLISHED')
->with('translations')
->orderBy('sort_order')
->get();
return view('pages.articles', [
'articles' => $articles->toArray(),
]);
}
Datele ajung direct in view, randate server-side, cu zero round-trip de retea dupa load.
Pasul 4: Ce am sters (fara frica)
Dupa migrare, am sters aproximativ 98 de fisiere React:
- Toate
.tsxdinresources/js/src/(pages, components, hooks, lib, providers) tsconfig.json,resources/js/app.tsx,resources/js/bootstrap.js- 45 dependencies din
package.json: react, react-dom, react-router-dom, @tanstack/react-query, 19 pachete @radix-ui, lucide-react, shadcn utilities
package.json final are doar devDependencies pentru Tailwind, Vite si Laravel plugin — fara dependencies de runtime.
Pasul 5: CSS — Tailwind ramane
Partea buna: Tailwind v4 functioneaza identic in Blade. Nu a trebuit sa rescriu niciun clasa CSS. Singura modificare a fost in tailwind.config.js — am schimbat path-urile de scanare de la .tsx la .blade.php:
content: [ "./resources/views/**/*.blade.php",
// "./resources/js/src/**/*.{ts,tsx}", <-- sters
],
Capcane pe care le-am intalnit
1. Vite manifest in teste CI
Dupa migrare, testele PHPUnit esuau cu ViteManifestNotFoundException pentru ca Blade layout-ul folosea @vite('resources/css/app.css'), iar CI-ul nu rula npm run build. Fix: $this->withoutVite() in setUp() al testelor care randeaza pagini.
2. Artefactul de release nu includea lang/
Am avut un release in care fisierele lang/ro.json si lang/en.json lipseau din artefact, iar site-ul live afisa cheile de traducere (nav.home in loc de "Home"). Fix: adaugare lang in config/updater.php -> include_paths.
3. Middleware SetLocale nu se aplica pe 404
Paginile de eroare sunt randate de Laravel in afara de middleware stack-ul rutei. Am adaugat detectia locale inline in resources/views/errors/404.blade.php:
$locale = request()->query('locale') ?? request()->cookie('locale')
?? session('locale')
?? config('app.locale', 'ro');
app()->setLocale($locale);
Cand NU ar trebui sa faci migrarea asta
Nu e un glont magic. Nu migra daca:
- Aplicatia ta e un dashboard cu multa stare si routing client-side
- Ai echipa frontend specializata in React si nicio experienta PHP
- Ai integrari complexe cu websockets sau streaming real-time
- Ai investit masiv in componente React custom sau design system
Migrarea are sens pentru site-uri publice cu continut predominant static: bloguri, portofolii, e-commerce small, site-uri de prezentare.
Rezultate finale
- ~98 fisiere React sterse, ~12.000 linii de cod eliminate
- ~45 dependente npm sterse,
node_modulesde 3x mai mic - Build time scazut de la 18s la 2s
- Core Web Vitals in zona verde pe toate metricile
- Indexare Google 100% vs ~40% inainte
- Paginat Filament admin neschimbat — continua sa mearga independent
Concluzie
Pentru site-ul meu, migrarea de la React SPA la Blade + Alpine.js a fost una dintre cele mai valoroase decizii tehnice din ultimul an. Mai putin cod de intretinut, performanta mai buna, SEO mai bun, build-uri rapide. Si cel mai important — pot sa scriu continut fara sa-mi fac griji ca e indexat corect.
Pentru setup-ul initial Laravel 12 + Vite + Tailwind v4, vezi Ghidul complet Laravel 12 + Vite + Tailwind v4.
Pentru comparatia detaliata intre SSR, SPA si SSG, citeste SSR vs SPA vs SSG — cand sa alegi fiecare.