logo
Firsttx
CSR 재방문에서 빈 화면 제거

CSR 재방문, 빈 화면은 끝.마지막 화면을 즉시 복구하는 3-레이어 툴킷.

FirstTx는 Prepaint, Local-First, Tx 세 레이어를 하나의 툴킷으로 묶었습니다. CSR 아키텍처는 그대로 두고, 재방문 복원·오프라인 내구성·안전한 낙관적 UI를 한 번에 더하세요.

내부 도구 / 대시보드오프라인 우선 앱대규모 CSR 앱
재방문 타임라인
FirstTx DevTools

FirstTx 도입 전

사용자 재방문

브라우저 뒤로 가기나 링크 클릭으로 CSR 앱으로 다시 진입합니다.

빈 화면 노출

JS 번들과 API 응답을 기다리며 1~2초 동안 흰 화면이나 스피너가 보입니다.

JS 로딩 & 마운트

React와 데이터 요청이 끝난 뒤에야 실제 화면이 렌더링됩니다.

FirstTx 도입 후

부트 스니펫

HTML 로드 직후, 작은 부트 스크립트가 한 번 실행됩니다.

마지막 화면 복원

IndexedDB에 저장된 마지막 DOM 스냅샷을 재방문 직후 바로 복원합니다.

Hydration & 동기화

사용자는 복원된 화면을 보고 있는 동안, React 마운트와 데이터 동기화가 백그라운드에서 진행됩니다.

View Transition API가 있을 경우 이 복원 과정을 부드러운 크로스페이드로 연출할 수 있습니다.
Revisits0 ms
Sync boilerplate↓ ~90%
Optimistic UIAtomic

THREE LAYERS · ONE TOOLKIT

Prepaint · Local-First · Tx

필요한 레이어만 골라 도입하세요. 재방문 속도는 Prepaint로, 동기화·낙관적 UI는 Local‑First와 Tx로 해결합니다.

React 19 + CSR 환경Vite / SPA / 내부 도구레이어별 선택 도입
PrepaintRender layer
LAYER 1

마지막 화면을 DOM 스냅샷으로 저장하고, 재방문 시 React보다 먼저 복원합니다.

  • 전체 화면 스냅샷을 IndexedDB에 보관
  • 재방문 시 JS 실행 전에 바로 복원
  • CSR 구조는 그대로 두고 SSR에 가까운 재방문 경험 제공
Local-FirstData layer
LAYER 2

IndexedDB를 단일 소스로 두고 서버 동기화를 자동화합니다.

  • zod 기반 타입 세이프 모델 정의
  • TTL·staleness 메타데이터 기본 제공
  • 백그라운드 동기화로 오프라인 친화적 플로우
TxExecution layer
LAYER 3

UI 업데이트를 트랜잭션으로 감싸 낙관적 업데이트를 안전하게 만듭니다.

  • 여러 단계를 하나의 트랜잭션으로 실행
  • 실패 시 보상(rollback) 로직 자동 실행
  • 네트워크 오류에서도 일관된 UI 상태 유지

HOW IT FEELS

앱에서 보이는 동작 방식

SSR은 첫 진입을 빠르게 해주지만, 실제 사용 시간은 재방문·뒤로 가기·탭 이동에 더 많이 쓰입니다. FirstTx는 이 순간들을 빠르게 만들어, 다시 올 때마다 준비된 화면을 보여줍니다.

재방문이 잦은 내부 도구

CRM·어드민·대시보드처럼 목록↔상세를 반복하는 화면에서 매번 새로 로딩하는 대신, 이전 상태를 바로 복원합니다.

작업 중 실수로 새로고침

로컬 모델이 최신 스냅샷을 들고 있어 필터·스크롤·폼 상태가 유지되고, 처음부터 다시 시작할 필요가 없습니다.

낙관적 UI가 실패했을 때

낙관적 업데이트를 트랜잭션으로 묶어 서버에서 거절되면 화면이 깔끔하게 되돌아가고, ‘반쯤만 롤백된 상태’를 피할 수 있습니다.

오프라인·불안정한 네트워크

간단한 동기화 훅 하나로, 오프라인/재연결을 견디면서도 유저의 위치와 컨텍스트를 잃지 않는 데이터 레이어를 만들 수 있습니다.

DevTools에서 보는 런타임 이벤트 예시레이어: prepaint · model · tx
prepaint.restorePrepaint
성공

IndexedDB 스냅샷 → DOM 복원 (4ms)

model.sync.startLocal-First
진행 중

TTL 초과로 백그라운드 동기화 시작

tx.commitTx
성공

UI 업데이트와 서버 요청 모두 성공

tx.rollbackTx
오류

네트워크 실패 → compensate로 UI 상태 되돌림

Chrome 확장 프로그램 FirstTx DevTools에서 위와 같은 이벤트들을 레이어별 타임라인으로 확인할 수 있습니다.

QUICK START

세 단계로 FirstTx 연결하기

패키지를 설치하고 Vite 플러그인을 켠 뒤, 루트 엔트리를 한 번 감싸기만 하면 됩니다. 처음에는 세 레이어를 모두 쓰거나, Prepaint부터 도입한 뒤 Local-First와 Tx를 나중에 추가해도 괜찮습니다.

React SPA / Vite레이어별 선택 도입
패키지 설치pnpm · npm · yarn
pnpm add @firsttx/prepaint @firsttx/local-first @firsttx/tx

# 선택적으로 필요한 레이어만 설치할 수도 있습니다.
pnpm add @firsttx/prepaint
pnpm add @firsttx/prepaint @firsttx/local-first
pnpm add @firsttx/local-first @firsttx/tx
1

1. Vite 플러그인 활성화

import { defineConfig } from "vite";
import { firstTx } from "@firsttx/prepaint/plugin/vite";

export default defineConfig({
  plugins: [firstTx()],
});
2

2. 엔트리 포인트 교체

import { createFirstTxRoot } from "@firsttx/prepaint";
import App from "./App";

createFirstTxRoot(
  document.getElementById("root")!,
  <App />
);

Local-First 모델 + 동기화 훅

Local-First
import { defineModel, useSyncedModel } from "@firsttx/local-first";
import { z } from "zod";

const CartModel = defineModel("cart", {
  schema: z.object({
    items: z.array(
      z.object({
        id: z.string(),
        name: z.string(),
        qty: z.number(),
      }),
    ),
  }),
});

function CartPage() {
  const { data: cart } = useSyncedModel(
    CartModel,
    () => fetch("/api/cart").then((r) => r.json()),
  );

  if (!cart) return <Skeleton />;

  return <div>{cart.items.length} items</div>;
}

Tx로 낙관적 업데이트를 트랜잭션으로 감싸기

UI를 먼저 업데이트한 뒤 서버와 동기화하고, 요청이 실패하면 되돌릴 수 있는 트랜잭션 경로를 제공합니다.

import { startTransaction } from "@firsttx/tx";

async function addToCart(item: CartItem) {
  const tx = startTransaction();

  await tx.run(
    () =>
      CartModel.patch((draft) => {
        draft.items.push(item);
      }),
    {
      compensate: () =>
        CartModel.patch((draft) => {
          draft.items.pop();
        }),
    },
  );

  await tx.run(() =>
    fetch("/api/cart", {
      method: "POST",
      body: JSON.stringify(item),
    }),
  );

  await tx.commit();
}