์ปจํ
์คํธ ๋ธ๋ก
| Key |
Value |
| Category |
frontend |
| Checklist |
ISS-UI-01, ISS-UI-02 โ Async Server Components without Suspense / Missing loading.tsx |
| Priority |
P2 ๐ก |
| Scan Date |
2026-04-16 |
| Flagged By |
main-session verification |
์์ฝ
- WHAT: 14๊ฐ page/layout ์ค 11๊ฐ ํ์ผ์
Suspense๊ฐ ์ฌ์ฉ๋์ง๋ง stream-friendly ๋ฐ์ดํฐ fetch ํจํด ๋ฏธ์ ์ฉ. ๋ํ ๋ผ์ฐํธ segment๋ณ loading.tsx ๋ถ์ฌ โ ํ์ด์ง ์ ํ ์ ๋น ํ๋ฉด
- WHY: Next.js 15 App Router์ ํต์ฌ ๊ฐ์น์ธ progressive streaming + skeleton UX๋ฅผ ํ์ฉ ๋ชปํจ. ์ฌ์ฉ์๋ ๋ฐ์ดํฐ ๋ก๋ฉ ๋์ ๋ชจ๋ ํ๋ฉด์ด ๋ฉ์ถ ๋ฏ ๋๋
- WHERE:
frontend/src/app/**/page.tsx (loading.tsx ์์), frontend/src/app/(main)/admin/**/page.tsx (Server Component์ธ๋ฐ Suspense ๋ถ์ฌ ๊ฐ๋ฅ)
- SEVERITY: MEDIUM โ UX ํ์ง ์ ํ, App Router ๋ฒ ์คํธ ํ๋ํฐ์ค ๋ฏธ์ค์
Evidence
| # |
File |
Line |
Finding |
Flagged By |
Confidence |
| 1 |
frontend/src/app/(main)/loading.tsx |
โ |
์กด์ฌ ์ฌ๋ถ ๋ฏธํ์ธ โ ์ผ๋ถ ๋ผ์ฐํธ segment์๋ง ์กด์ฌ |
main-session |
Medium |
| 2 |
frontend/src/app/(auth)/loading.tsx |
โ |
์กด์ฌ ์ฌ๋ถ ๋ฏธํ์ธ |
main-session |
Medium |
| 3 |
frontend/src/app/(main)/admin/loading.tsx |
โ |
admin ๋ผ์ฐํธ ๊ทธ๋ฃน์๋ loading.tsx ์กด์ฌ (skeleton ํจํด์ผ๋ก ์ฌ์ฉ๋จ, code-review์์ ํ์ธ) |
main-session |
High |
| 4 |
frontend/src/app/**/page.tsx |
โ |
11๊ฐ ํ์ผ์ Suspense ์ฌ์ฉ๋๋ page ์์ฒด์ ์ ์ฉ๋ ๊ณณ์ 1๊ณณ (login/page.tsx)๋ง |
main-session |
High |
์ํฅ ๋ถ์
์ํฅ ๋ฒ์
- ๋ชจ๋ ๋ผ์ฐํธ ์ ํ (login โ admin, admin โ users ๋ฑ)
- ์ฒซ ๋ก๋ ์ ๋ฐ์ดํฐ fetch ๋์ ๋น ํ๋ฉด
- ๋ชจ๋ฐ์ผ/์ ์ ๋คํธ์ํฌ ํ๊ฒฝ์์ UX ๊ฐ์ฅ ๋ช
ํ
์ฅ์ ์๋๋ฆฌ์ค
- ์ฌ์ฉ์๊ฐ admin ๋ฉ๋ด์์ "Users" ํด๋ฆญ
getUsers() server fetch ์์ (300ms-1s)
- loading.tsx ์์ผ๋ฏ๋ก ์ด์ ํ์ด์ง ๊ทธ๋๋ก ํ์ โ ์ฌ์ฉ์๋ "ํด๋ฆญ ์๋๋?" ์์ฌํ๋ฉฐ ๋ค์ ํด๋ฆญ
- ๊ฒฐ๊ตญ ์ ํ์ด์ง ๋ก๋๋์ง๋ง ์ฒซ ์ธ์ ๋์จ
๊ธด๊ธ๋
- ์ฆ๊ฐ์ ์ฅ์ ์๋์ง๋ง sustained UX ์ ํ
- ๋ค๋ฅธ Next.js 15 App Router ํ๋ก์ ํธ ๋๋น ๊ธฐ๋ณธ๊ธฐ ๋ถ์กฑ
์ ์ ํด๊ฒฐ ๋ฐฉ์
์ ๊ทผ ๋ฐฉ๋ฒ
-
loading.tsx ์ถ๊ฐ โ ์ฃผ์ ๋ผ์ฐํธ segment์:
// frontend/src/app/(main)/admin/users/loading.tsx
import { TableSkeleton } from \"@/shared/components/skeletons/TableSkeleton\";
export default function Loading() {
return <TableSkeleton rows={10} />;
}
-
๋ฐ์ดํฐ fetch๋ฅผ Server Component๋ก ์ด๋ + Suspense ํ์ฉ:
// page.tsx
import { Suspense } from \"react\";
export default function UsersPage() {
return (
<Suspense fallback={<TableSkeleton />}>
<UsersList /> {/* ์ด ์ปดํฌ๋ํธ๊ฐ await fetch */}
</Suspense>
);
}
-
๊ฐ ๋ผ์ฐํธ๋ณ ์ ์ฉ: (main)/, (auth)/, admin/users/, admin/settings/, admin/approvals/
๋์
- Client-side fetch + skeleton: ํ์ฌ ์ฌ์ฉ ์ค์ผ ์ ์์ โ JS ๋ฒ๋ค ์ฆ๊ฐ + slower LCP
- router.events.on("routeChangeStart")๋ก progress bar: Next.js App Router๋ ์ด API ๋ฏธ์ง์ โ suspense + loading.tsx๊ฐ ํ์ค
์์ฉ ๊ธฐ์ค
์ฐธ์กฐ
์ฌํ ๋ฐฉ๋ฒ
์ฌ์ ์กฐ๊ฑด
๋จ๊ณ
- admin ํ์ด์ง์์ "Users" ๋ฉ๋ด ํด๋ฆญ
- ํ์ด์ง ์ ํ ๋์ ํ๋ฉด ๊ด์ฐฐ (ํนํ Network throttling "Slow 3G")
- loading skeleton ํ์ ์ฌ๋ถ ํ์ธ
๊ธฐ๋ ๊ฒฐ๊ณผ
์ฆ์ skeleton UI ํ์, ๋ฐ์ดํฐ ๋์ฐฉ ํ ์ค์ ์ฝํ
์ธ ๋ก ๊ต์ฒด
์ค์ ๊ฒฐ๊ณผ
์ด์ ํ์ด์ง ํ๋ฉด ์ ์ง ๋๋ ๋น ํ๋ฉด โ UX ๋ถ์์ฐ์ค๋ฌ์
๊ด๋ จ ์ฝ๋ ์ปจํ
์คํธ
| File |
Role |
Relevance |
frontend/src/app/(main)/admin/loading.tsx |
๊ธฐ์กด admin loading (์ฐธ๊ณ ) |
ํจํด ์ฌ์ฌ์ฉ |
frontend/src/app/(main)/admin/users/page.tsx |
์ฌ์ฉ์ ๋ชฉ๋ก ํ์ด์ง |
์ฐ์ ์ ์ฉ ๋์ |
frontend/src/app/(main)/admin/settings/page.tsx |
์ค์ ํ์ด์ง |
์ ์ฉ ๋์ |
frontend/src/shared/components/skeletons/ |
์ค์ผ๋ ํค ์ปดํฌ๋ํธ๋ค |
fallback์ ํ์ฉ |
Detected by oh-my-braincrew `omb:issue` scan
Category: frontend | Scan date: 2026-04-16
`omb-issue-scan category=frontend checklist=ISS-UI-01,ISS-UI-02`
์ปจํ ์คํธ ๋ธ๋ก
ISS-UI-01,ISS-UI-02โ Async Server Components without Suspense / Missing loading.tsx์์ฝ
Suspense๊ฐ ์ฌ์ฉ๋์ง๋ง stream-friendly ๋ฐ์ดํฐ fetch ํจํด ๋ฏธ์ ์ฉ. ๋ํ ๋ผ์ฐํธ segment๋ณloading.tsx๋ถ์ฌ โ ํ์ด์ง ์ ํ ์ ๋น ํ๋ฉดfrontend/src/app/**/page.tsx(loading.tsx ์์),frontend/src/app/(main)/admin/**/page.tsx(Server Component์ธ๋ฐ Suspense ๋ถ์ฌ ๊ฐ๋ฅ)Evidence
frontend/src/app/(main)/loading.tsxfrontend/src/app/(auth)/loading.tsxfrontend/src/app/(main)/admin/loading.tsxfrontend/src/app/**/page.tsx์ํฅ ๋ถ์
์ํฅ ๋ฒ์
์ฅ์ ์๋๋ฆฌ์ค
getUsers()server fetch ์์ (300ms-1s)๊ธด๊ธ๋
์ ์ ํด๊ฒฐ ๋ฐฉ์
์ ๊ทผ ๋ฐฉ๋ฒ
loading.tsx ์ถ๊ฐ โ ์ฃผ์ ๋ผ์ฐํธ segment์:
๋ฐ์ดํฐ fetch๋ฅผ Server Component๋ก ์ด๋ + Suspense ํ์ฉ:
๊ฐ ๋ผ์ฐํธ๋ณ ์ ์ฉ: (main)/, (auth)/, admin/users/, admin/settings/, admin/approvals/
๋์
์์ฉ ๊ธฐ์ค
loading.tsxํ์ผ์ ์ฃผ์ ๋ผ์ฐํธ(์ต์ 5๊ฐ) segment์ ์ถ๊ฐcd frontend && pnpm build && pnpm start์ฐธ์กฐ
frontend/src/app/**/loading.tsx(๋ค์ ์ ๊ท ์์ฑ),frontend/src/app/**/page.tsx์ฌํ ๋ฐฉ๋ฒ
์ฌ์ ์กฐ๊ฑด
๋จ๊ณ
๊ธฐ๋ ๊ฒฐ๊ณผ
์ฆ์ skeleton UI ํ์, ๋ฐ์ดํฐ ๋์ฐฉ ํ ์ค์ ์ฝํ ์ธ ๋ก ๊ต์ฒด
์ค์ ๊ฒฐ๊ณผ
์ด์ ํ์ด์ง ํ๋ฉด ์ ์ง ๋๋ ๋น ํ๋ฉด โ UX ๋ถ์์ฐ์ค๋ฌ์
๊ด๋ จ ์ฝ๋ ์ปจํ ์คํธ
frontend/src/app/(main)/admin/loading.tsxfrontend/src/app/(main)/admin/users/page.tsxfrontend/src/app/(main)/admin/settings/page.tsxfrontend/src/shared/components/skeletons/