--- id: frontend-progressive-enhancement title: Progressive Enhancement — Server-first / 점진 JS category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [frontend, progressive-enhancement, html, vibe-coding] tech_stack: { language: "HTML / TS", applicable_to: ["Frontend"] } applied_in: [] aliases: [progressive enhancement, server-first, no-JS fallback, HTMX, web standards, MPA] --- # Progressive Enhancement > Server-rendered HTML 부터 → JS 로 향상. **JS 없어도 작동, 있으면 더 좋음**. Remix / Next App Router / HTMX / web standards. SPA 의 reaction. ## 📖 핵심 개념 - HTML 가 baseline. - Form / link 가 native (JS 없어도). - JS = enhancement. - 학교 wifi / slow 3G / 옛 browser 도 작동. ## 💻 코드 패턴 ### 기본 — Form 가 native ```html
``` → JS 없이 작동. 검증도 native. ### Next.js Server Action (PE) ```tsx // app/login/page.tsx async function login(formData: FormData) { 'use server'; const email = formData.get('email') as string; const password = formData.get('password') as string; // ... auth redirect('/dashboard'); } export default function LoginPage() { return ( ); } ``` → JS 없어도 form submit. JS 가 활성화되면 SPA-like UX. ### useFormStatus (loading state) ```tsx 'use client'; import { useFormStatus } from 'react-dom'; function SubmitButton() { const { pending } = useFormStatus(); return ; } ``` → JS 활성 시 loading 표시. JS 없으면 기본 button. ### Remix loader / action ```tsx // app/routes/login.tsx export async function action({ request }: ActionArgs) { const formData = await request.formData(); const email = formData.get('email'); // ... return redirect('/dashboard'); } export default function Login() { return ( ); } ``` → Remix ` 42 ``` ### Native dialog (JS 안) ```html ``` → JS 안 쓰고도 modal. (showModal 만 JS — fallback 가능). ### View Transitions (declarative animation) ```html About ``` ```css @view-transition { navigation: auto; } ``` ### Anchor link / form / radio = SPA-like 가능 ```htmlAnswer