불러오는 중...
불러오는 중...
Notes from rebuilding sapan.dev with the View Transitions API for navigation across 16 locales. Replaced an entire Framer Motion orchestration layer with a CSS file, found one annoying flash on RTL Arabic, and walked away with a much smaller bundle.
Earlier this year I rebuilt sapan.dev to use the View Transitions API for navigation across 16 locales. The previous version used Framer Motion variants to coordinate layout shifts between routes — it worked, but every route change had to be orchestrated by hand and the bundle was paying for it. Switching to View Transitions replaced roughly 200 lines of motion-orchestration code with a small CSS file.
For years, smooth page transitions on the web required frameworks like Framer Motion or SPA libraries that hijack navigation. The View Transitions API changes that. It is a native browser API that captures the old DOM, renders the new one, and animates between them — declaratively, with CSS.
A view transition is a snapshot plus a crossfade. The browser captures the current visual state of the page as an image, updates the DOM, captures the new state, and animates between the two. You control the animation with CSS pseudo-elements like ::view-transition-old and ::view-transition-new.
// Trigger a same-document transition imperatively
document.startViewTransition(() => {
// Any DOM updates inside this callback are captured
updateContent();
});
// With async updates (e.g. after fetching data)
document.startViewTransition(async () => {
const data = await fetch('/api/posts').then((r) => r.json());
renderPosts(data);
});Same-document transitions work within a single page — SPA route changes, tab switches, modal opens. Cross-document transitions work across full page loads on multi-page apps. Both use the same CSS API; only the trigger differs.
<!-- Opt in to cross-document transitions in the old and new pages -->
<meta name="view-transition" content="same-origin" />
<!-- Or via CSS -->
<style>
@view-transition {
navigation: auto;
}
</style>정보
Cross-document view transitions require both pages to opt in. Navigate between two pages that both declare the API and the browser coordinates the transition automatically.
The real magic appears with view-transition-name. Two elements with the same name across the old and new DOM are treated as the same element — the browser interpolates their position, size, and styles. This is how you get Apple-style hero image transitions with nothing but CSS.
/* On the thumbnail in the list view */
.project-card img {
view-transition-name: project-hero;
}
/* On the full image in the detail view */
.project-detail img {
view-transition-name: project-hero;
}
/* Customize the morph animation */
::view-transition-old(project-hero),
::view-transition-new(project-hero) {
animation-duration: 400ms;
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
}Next.js 15+ supports view transitions as an experimental flag. When enabled, soft navigations (Link clicks) trigger a same-document view transition automatically. You customize the animation purely in CSS.
// next.config.ts
export default {
experimental: {
viewTransition: true,
},
};
// app/globals.css — customize all transitions
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 300ms;
animation-timing-function: ease-out;
}The View Transitions API is supported in Chromium-based browsers and Safari 18+. Firefox support is in progress. For unsupported browsers, the API simply no-ops — your navigation works without animation. Feature-detect if you need to branch on support.
경고
Respect prefers-reduced-motion. Wrap your view-transition-name declarations in @media (prefers-reduced-motion: no-preference) so users who opt out of animation get instant navigation.
Rebuilding sapan.dev with View Transitions saved me an entire animation library and made the portfolio feel snappier. The Framer Motion variants for route transitions are deleted, the bundle is smaller, and animations run on the compositor instead of the main thread. One caveat worth flagging: getting it to feel right with RTL Arabic took a couple of tries — view-transition-name on a flex-reversed layout produced a horizontal flash that I eventually fixed by scoping view-transition-name only to LTR locales. For most projects though, this is a clear win and one of the best browser additions in years.
CSS의 다른 글
Modern CSS features I now reach for in every project — and the specific moments on the Templately admin and sapan.dev where each one replaced a chunk of JavaScript or some BEM gymnastics that had been there for years.
sapan.dev runs on Tailwind v4 — no `tailwind.config.js`, the design tokens live in CSS, and the build is dramatically faster. Notes from the migration off v3 and what the new architecture actually changes day-to-day.
sapan.dev's design tokens are oklch all the way down. The brand color is one variable; every shade, hover, and disabled state derives from it programmatically. Notes on what oklch and color-mix actually buy you in a design system.