2026-08-14 · 10 min
Astro View Transitions vs SPA Navigation: Benchmark Nyata Blog + Dashboard Hybrid
Klien saya di Jakarta minta satu aplikasi yang fungsinya separuh blog editorial (50+ artikel, SEO penting) dan separuh dashboard analitik internal (chart real-time, filter interaktif, beberapa form). Mereka tidak mau mengelola dua codebase. Pertanyaannya langsung ke inti: Astro dengan View Transitions, atau Next.js App Router sebagai SPA?
Saya habiskan dua minggu menjalankan keduanya di production-like environment, mengukur angka nyata, lalu membuat keputusan. Ini laporannya.
Setup Benchmark
Dua implementasi dibangun dengan skenario identik: 12 halaman artikel (markdown, rata-rata 1.400 kata), 3 halaman dashboard (tabel data, line chart via Chart.js, satu form filter dengan tanggal range). Dihosting di Cloudflare Pages. Pengukuran dilakukan via WebPageTest dari node Singapura dengan profil throttling Motorola G5 + 4G LTE.
Stack A — Astro 5.x + View Transitions:
src/
pages/
blog/[slug].astro # MPA route
dashboard/
analytics.astro
users.astro
reports.astro
components/
ChartIsland.tsx # React island, client:load
FilterForm.tsx # React island, client:visible
Konfigurasi View Transitions di layout utama:
---
// layouts/Base.astro
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<nav transition:persist>
<!-- nav tidak di-remount saat navigasi -->
</nav>
<main>
<slot />
</main>
</body>
</html>
Stack B — Next.js 15 App Router:
app/
blog/[slug]/page.tsx # RSC
dashboard/
analytics/page.tsx
users/page.tsx
reports/page.tsx
components/
ChartClient.tsx # 'use client'
FilterForm.tsx # 'use client'
Keduanya memakai data yang identik, font yang sama (Inter via font-display: swap), dan tidak ada perbedaan logika bisnis.
Angka yang Keluar
Metrik diambil dari rata-rata 5 run WebPageTest, initial load dari homepage lalu navigasi ke halaman artikel, lalu ke dashboard analytics.
Initial Load (Homepage):
| Metrik | Astro + VT | Next.js 15 |
|---|---|---|
| TTFB | 142ms | 198ms |
| LCP | 1.2s | 1.6s |
| TBT (Total Blocking Time) | 12ms | 89ms |
| JS Bundle (gzip) | 18 KB | 187 KB |
| TTI | 1.4s | 2.1s |
Perbedaan JS bundle adalah yang paling mencolok. Astro hanya mengirim JavaScript untuk island yang ada di halaman tersebut. Next.js membawa router, RSC protocol, dan hydration runtime sejak awal.
Navigasi ke Halaman Artikel (subsequent navigation):
| Metrik | Astro + VT | Next.js 15 |
|---|---|---|
| LCP | 0.9s | 0.4s |
| Layout Shift (CLS) | 0.02 | 0.01 |
| Durasi animasi transisi | 300ms | 250ms (custom) |
Di sini Next.js mulai unggul. Karena konten artikel sudah di-prefetch sebagai RSC payload (JSON, bukan HTML), navigasi kedua terasa instan — browser tidak perlu parse HTML baru, hanya swap komponen. Astro melakukan fetch HTML lengkap dari edge, tapi karena Cloudflare Pages menyimpannya di edge cache, round-trip tetap cepat. Bukan kalah telak, tapi terasa.
Navigasi ke Dashboard (halaman dengan React island berat):
| Metrik | Astro + VT | Next.js 15 |
|---|---|---|
| LCP | 1.1s | 0.6s |
| Time to Interactive (island) | 1.8s | 1.1s |
| JS yang di-load | 94 KB | 187 KB |
Next.js menang di sini karena bundle sudah ada di memory — tidak ada re-download. Astro harus memuat ulang island JavaScript-nya walau halaman sudah dikunjungi, karena cache browser tidak menjamin modul tetap di memori.
Mengonfigurasi View Transitions untuk Shared Elements
Salah satu fitur paling berguna yang sering dilewatkan adalah animasi shared element — elemen yang “terbang” dari satu halaman ke halaman berikutnya. Untuk blog dengan thumbnail artikel yang muncul di listing lalu menjadi hero image di detail, ini signifikan secara UX:
<!-- Di halaman listing (blog/index.astro) -->
<img
src={post.cover}
alt={post.title}
transition:name={`cover-${post.slug}`}
transition:animate="fade"
/>
<!-- Di halaman detail (blog/[slug].astro) -->
<img
src={entry.data.cover}
alt={entry.data.title}
transition:name={`cover-${entry.slug}`}
class="hero-image"
/>
Browser mendeteksi kedua elemen dengan transition:name yang sama, lalu menghitung transformasi posisi dan ukurannya secara otomatis. Tidak perlu menghitung posisi manual dengan JavaScript.
Untuk dashboard, saya pakai transition:persist agar sidebar dan header tidak di-remount, yang mencegah flicker visual saat berpindah halaman:
<aside transition:persist="sidebar">
<DashboardNav client:load />
</aside>
Trade-off yang Saya Temukan di Production
Yang membuat saya sempat frustrasi dengan View Transitions:
Scroll position tidak dipreservasi secara otomatis di semua browser. Di Firefox 130, saat kembali ke halaman listing setelah membaca artikel, posisi scroll tidak kembali ke posisi kartu yang diklik. Saya harus menambah handler manual:
// Simpan posisi scroll sebelum navigasi
document.addEventListener('astro:before-preparation', () => {
sessionStorage.setItem(
`scroll-${location.pathname}`,
window.scrollY.toString()
);
});
// Pulihkan setelah navigasi selesai
document.addEventListener('astro:page-load', () => {
const saved = sessionStorage.getItem(`scroll-${location.pathname}`);
if (saved) {
window.scrollTo({ top: parseInt(saved), behavior: 'instant' });
sessionStorage.removeItem(`scroll-${location.pathname}`);
}
});
Yang membuat saya lebih memilih Astro untuk proyek ini:
Satu hal yang tidak muncul di benchmark tapi sangat terasa di production: cold start behavior. Next.js App Router dengan Vercel/Cloudflare mengalami cold start tiap kali serverless function belum warm. Untuk dashboard yang dikunjungi pagi hari setelah semalam tidak ada traffic, TTFB bisa menyentuh 800ms–1.2s. Astro yang sepenuhnya pre-rendered dan disajikan dari edge CDN konsisten di 80–150ms tanpa peduli kapan terakhir dikunjungi.
Untuk konten editorial yang mayoritas statis, ini perbedaan yang terasa secara nyata bagi pengguna.
Yang harus Anda relakan dengan Astro:
- Tidak ada optimistic UI tanpa implementasi manual. Di form dashboard saya, setelah submit filter, ada full navigation cycle karena page-based routing. Saya mitigasi dengan
client:loadisland yang mengambil data secara mandiri via fetch tanpa routing. - Global state yang survive navigasi butuh Nanostores atau solusi eksternal:
// stores/dashboardFilter.ts
import { atom } from 'nanostores';
export const dateRange = atom<{ from: string; to: string }>({
from: new Date(Date.now() - 7 * 86400000).toISOString().slice(0, 10),
to: new Date().toISOString().slice(0, 10),
});
Island React cukup subscribe ke store ini. State tetap ada saat navigasi antar halaman dashboard karena Nanostores hidup di module scope JavaScript, bukan di React component tree.
Keputusan Akhir
Untuk proyek blog + dashboard hybrid ini, saya pilih Astro dengan View Transitions. Alasannya:
-
SEO dan initial load menang jelas. Blog editorial membutuhkan LCP yang baik untuk indexing dan Core Web Vitals. Selisih 400ms LCP di initial load adalah perbedaan yang cukup besar di peringkat pencarian.
-
Cold start tidak ada. Dashboard internal yang diakses pagi hari tidak perlu menunggu serverless warm-up.
-
Kompleksitas lebih rendah. Tim yang mengelola proyek ini bukan full-time frontend engineer — dua backend developer yang sesekali menyentuh UI. Astro dengan island architecture lebih mudah dipahami dan diprediksi daripada RSC mental model Next.js.
-
Subsequent navigation cukup baik. Selisih 500ms di navigasi kedua terasa, tapi tidak cukup menyakitkan untuk kalangan pengguna internal yang sudah terbiasa dengan aplikasi web biasa.
Saya akan pilih Next.js kalau: mayoritas pengguna adalah heavy user yang berpindah halaman puluhan kali per sesi (misal aplikasi admin dengan banyak workflow berurutan), atau kalau dibutuhkan optimistic mutation yang kompleks seperti aplikasi task management real-time.
Untuk sebagian besar proyek hybrid yang saya lihat di startup Indonesia — blog produk + dashboard ringan — Astro dengan View Transitions adalah pilihan yang lebih tepat. JavaScript lebih sedikit, initial load lebih cepat, dan kompleksitas yang sepadan dengan kebutuhan nyata.
Ditulis oleh Reza Pradipta