posts

Expo Router Forum App — **Scaffold v2 (layout-first, `@folder:*` aliases, f/*)

Sep 18, 2025 updated Sep 18, 2025 architectureexporeact-nativestylingux

Expo Router Forum App — Scaffold v2 (layout-first, @folder:* aliases, f/*)

요구 반영 사항

  • 레이아웃 우선 패턴: 각 화면 파일에서는 <Stack.Screen>을 쓰지 않고, **해당 서브트리의 _layout.tsx**에서 <Stack.Screen name="…">으로 스크린 옵션/타이틀을 중앙 관리합니다.
  • 케밥 케이스: 폴더/파일은 모두 kebab-case, 컴포넌트 내부 이름만 PascalCase.

1) Directory Tree (라우팅)

app/
├─ _layout.tsx
├─ +html.tsx
├─ +not-found.tsx
│
├─ (auth)/
│  ├─ _layout.tsx
│  ├─ sign-in.tsx
│  ├─ sign-up/
│  │  ├─ _layout.tsx
│  │  ├─ index.tsx
│  │  ├─ enter-info.tsx
│  │  └─ complete.tsx
│  └─ find-credentials/
│     ├─ _layout.tsx
│     ├─ index.tsx
│     └─ reset-password.tsx
│
├─ (shell)/                        # Tabs: home / f / search / inbox / my
│  ├─ _layout.tsx
│  ├─ index.tsx
│  ├─ f/
│  │  ├─ _layout.tsx
│  │  ├─ index.tsx
│  │  └─ [sub]/
│  │     ├─ _layout.tsx            # ← (중요) 하위 스크린 옵션을 여기에서 일괄 지정
│  │     ├─ index.tsx
│  │     ├─ about.tsx
│  │     ├─ rules.tsx
│  │     ├─ wiki/
│  │     │  ├─ index.tsx
│  │     │  └─ [page].tsx
│  │     └─ post/
│  │        └─ (.)[post-id].tsx    # 인터셉트 미리보기(탭 유지 모달)
│  ├─ search/
│  │  ├─ _layout.tsx
│  │  ├─ index.tsx
│  │  ├─ users.tsx
│  │  ├─ communities.tsx
│  │  └─ posts.tsx
│  ├─ inbox/
│  │  ├─ _layout.tsx
│  │  └─ index.tsx
│  └─ my/
│     ├─ _layout.tsx
│     ├─ index.tsx
│     ├─ my-posts.tsx
│     ├─ my-comments.tsx
│     ├─ saved.tsx
│     └─ drafts.tsx
│
├─ (stack)/                        # 탭 제거 풀스크린
│  ├─ _layout.tsx
│  ├─ f/
│  │  └─ [sub]/
│  │     ├─ _layout.tsx            # ← (중요) 정식 상세/작성 등 옵션 일괄 지정
│  │     ├─ submit/
│  │     │  ├─ index.tsx
│  │     │  └─ preview.tsx
│  │     ├─ comments/
│  │     │  └─ [post-id]/
│  │     │     ├─ index.tsx
│  │     │     ├─ edit.tsx
│  │     │     └─ comment/
│  │     │        └─ [comment-id].tsx
│  │     └─ mod/
│  │        ├─ _layout.tsx
│  │        ├─ queue.tsx
│  │        ├─ reports.tsx
│  │        └─ settings.tsx
│  ├─ u/
│  │  └─ [username]/
│  │     ├─ _layout.tsx
│  │     ├─ index.tsx
│  │     ├─ posts.tsx
│  │     ├─ comments.tsx
│  │     └─ relations/
│  │        ├─ followers.tsx
│  │        └─ following.tsx
│  └─ settings/
│     ├─ _layout.tsx
│     ├─ index.tsx
│     ├─ profile.tsx
│     ├─ account.tsx
│     ├─ privacy.tsx
│     ├─ notifications.tsx
│     ├─ appearance.tsx
│     └─ data/
│        ├─ export.tsx
│        └─ delete.tsx
│
├─ (modal)/
│  ├─ _layout.tsx
│  ├─ image-picker.tsx
│  ├─ share.tsx
│  ├─ report.tsx
│  └─ user-preview/[username].tsx
│
└─ p/
   └─ [short-id].tsx

2) 루트 & 공통

app/_layout.tsx

import { Stack } from "expo-router";
import { SafeAreaProvider } from "react-native-safe-area-context";
import getDefaultStackScreenOptions from "@folder:template/config-screen";

const LayoutRoot = () => {
  return (
    <SafeAreaProvider>
      <Stack screenOptions={getDefaultStackScreenOptions()}>
        <Stack.Screen name="(shell)" options={{ headerShown: false }} />
        <Stack.Screen name="(stack)" options={{ headerShown: false }} />
        <Stack.Screen name="(modal)" options={{ headerShown: false }} />
        <Stack.Screen name="(auth)"  options={{ headerShown: false }} />
      </Stack>
    </SafeAreaProvider>
  );
};

export default LayoutRoot;

app/+html.tsx

import React from "react";
import { ScrollViewStyleReset } from "expo-router/html";
import "../style/unistyles"; // configure는 index.ts에서도 1회 호출됨(중복 호출 금지)

const HTML = ({ children }: React.PropsWithChildren) => (
  <html>
    <head><ScrollViewStyleReset /></head>
    <body>{children}</body>
  </html>
);

export default HTML;

app/+not-found.tsx

import { Link } from "expo-router";
import { View, Text } from "react-native";

const NotFound = () => (
  <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
    <Text>페이지를 찾을 수 없습니다.</Text>
    <Link href="/(shell)" style={{ marginTop: 12 }}>홈으로</Link>
  </View>
);

export default NotFound;

3) (auth)

app/(auth)/_layout.tsx

import { Stack } from "expo-router";
import getDefaultStackScreenOptions from "@folder:template/config-screen";

const LayoutAuth = () => (
  <Stack screenOptions={getDefaultStackScreenOptions()}>
    <Stack.Screen name="sign-in" options={{ title: "로그인" }} />
    <Stack.Screen name="sign-up" options={{ headerShown: false }} />
    <Stack.Screen name="find-credentials" options={{ headerShown: false }} />
  </Stack>
);

export default LayoutAuth;

app/(auth)/sign-in.tsx

import ScreenSignIn from "@folder:screen/sign-in/screen-sign-in";
const RouteSignIn = () => <ScreenSignIn />;
export default RouteSignIn;

app/(auth)/sign-up/_layout.tsx

import { Stack } from "expo-router";
import getDefaultStackScreenOptions from "@folder:template/config-screen";

const LayoutSignUp = () => (
  <Stack screenOptions={getDefaultStackScreenOptions()}>
    <Stack.Screen name="index"      options={{ title: "약관 동의" }} />
    <Stack.Screen name="enter-info" options={{ title: "정보 입력" }} />
    <Stack.Screen name="complete"   options={{ title: "가입 완료" }} />
  </Stack>
);

export default LayoutSignUp;

app/(auth)/sign-up/index.tsx

import ScreenSignUpTerms from "@folder:screen/sign-up/screen-sign-up-terms";
const RouteSignUpTerms = () => <ScreenSignUpTerms />;
export default RouteSignUpTerms;

app/(auth)/sign-up/enter-info.tsx

import ScreenSignUpEnterInfo from "@folder:screen/sign-up/screen-sign-up-enter-info";
const RouteSignUpEnterInfo = () => <ScreenSignUpEnterInfo />;
export default RouteSignUpEnterInfo;

app/(auth)/sign-up/complete.tsx

import ScreenSignUpComplete from "@folder:screen/sign-up/screen-sign-up-complete";
const RouteSignUpComplete = () => <ScreenSignUpComplete />;
export default RouteSignUpComplete;

app/(auth)/find-credentials/_layout.tsx

import { Stack } from "expo-router";
import getDefaultStackScreenOptions from "@folder:template/config-screen";

const LayoutFindCreds = () => (
  <Stack screenOptions={getDefaultStackScreenOptions()}>
    <Stack.Screen name="index"          options={{ title: "계정 찾기" }} />
    <Stack.Screen name="reset-password" options={{ title: "비밀번호 재설정" }} />
  </Stack>
);

export default LayoutFindCreds;

app/(auth)/find-credentials/index.tsx

import ScreenFindCreds from "@folder:screen/find-credentials/screen-find-credentials";
const RouteFindCreds = () => <ScreenFindCreds />;
export default RouteFindCreds;

app/(auth)/find-credentials/reset-password.tsx

import ScreenResetPassword from "@folder:screen/find-credentials/screen-reset-password";
const RouteResetPassword = () => <ScreenResetPassword />;
export default RouteResetPassword;

4) (shell) — Tabs & f/*

app/(shell)/_layout.tsx

import { Tabs } from "expo-router";
import Ionicons from "@expo/vector-icons/Ionicons";

const LayoutShell = () => (
  <Tabs screenOptions={{ headerShown: false }}>
    <Tabs.Screen name="index" options={{ title: "Home", tabBarIcon: ({ color, focused }) => (
      <Ionicons name={focused ? "home" : "home-outline"} color={color} size={22} />
    )}} />
    <Tabs.Screen name="f" options={{ title: "Forums", tabBarIcon: ({ color, focused }) => (
      <Ionicons name={focused ? "albums" : "albums-outline"} color={color} size={22} />
    )}} />
    <Tabs.Screen name="search" options={{ title: "Search", tabBarIcon: ({ color, focused }) => (
      <Ionicons name={focused ? "search" : "search-outline"} color={color} size={22} />
    )}} />
    <Tabs.Screen name="inbox" options={{ title: "Inbox", tabBarIcon: ({ color, focused }) => (
      <Ionicons name={focused ? "mail" : "mail-outline"} color={color} size={22} />
    )}} />
    <Tabs.Screen name="my" options={{ title: "My", tabBarIcon: ({ color, focused }) => (
      <Ionicons name={focused ? "person" : "person-outline"} color={color} size={22} />
    )}} />
  </Tabs>
);

export default LayoutShell;

app/(shell)/index.tsx

import ScreenHome from "@folder:screen/home/home-screen";
const RouteHome = () => <ScreenHome />;
export default RouteHome;

app/(shell)/f/_layout.tsx

import { Stack } from "expo-router";
import getDefaultStackScreenOptions from "@folder:template/config-screen";

const LayoutF = () => (
  <Stack screenOptions={getDefaultStackScreenOptions()}>
    <Stack.Screen name="index" options={{ title: "포럼 탐색" }} />
    <Stack.Screen name="[sub]" options={{ headerShown: false }} />
  </Stack>
);

export default LayoutF;

app/(shell)/f/index.tsx

import ScreenForumsExplore from "@folder:screen/forums/screen-forums-explore";
const RouteForumsExplore = () => <ScreenForumsExplore />;
export default RouteForumsExplore;

app/(shell)/f/[sub]/_layout.tsx

import { Stack } from "expo-router";
import getDefaultStackScreenOptions from "@folder:template/config-screen";

const LayoutSub = () => (
  <Stack screenOptions={getDefaultStackScreenOptions()}>
    <Stack.Screen name="index"              options={{ title: "포럼" }} />
    <Stack.Screen name="about"              options={{ title: "정보" }} />
    <Stack.Screen name="rules"              options={{ title: "규칙" }} />
    <Stack.Screen name="wiki/index"         options={{ title: "위키" }} />
    <Stack.Screen name="wiki/[page]"        options={{ title: "위키" }} />
    <Stack.Screen name="post/(.)[post-id]"  options={{ title: "미리보기", presentation: "modal" }} />
  </Stack>
);

export default LayoutSub;

app/(shell)/f/[sub]/index.tsx

import ScreenSubFeed from "@folder:screen/forums/screen-sub-feed";
const RouteSubFeed = () => <ScreenSubFeed />;
export default RouteSubFeed;

app/(shell)/f/[sub]/about.tsx

import ScreenSubAbout from "@folder:screen/forums/screen-sub-about";
const RouteSubAbout = () => <ScreenSubAbout />;
export default RouteSubAbout;

app/(shell)/f/[sub]/rules.tsx

import ScreenSubRules from "@folder:screen/forums/screen-sub-rules";
const RouteSubRules = () => <ScreenSubRules />;
export default RouteSubRules;

app/(shell)/f/[sub]/wiki/index.tsx

import ScreenSubWiki from "@folder:screen/forums/screen-sub-wiki";
const RouteSubWiki = () => <ScreenSubWiki />;
export default RouteSubWiki;

app/(shell)/f/[sub]/wiki/[page].tsx

import ScreenSubWikiPage from "@folder:screen/forums/screen-sub-wiki-page";
const RouteSubWikiPage = () => <ScreenSubWikiPage />;
export default RouteSubWikiPage;

app/(shell)/f/[sub]/post/(.)[post-id].tsx

import ScreenPostPreview from "@folder:screen/post/screen-post-preview";
const RoutePostPreview = () => <ScreenPostPreview />;
export default RoutePostPreview;

app/(shell)/search/_layout.tsx

import { Stack } from "expo-router";
import getDefaultStackScreenOptions from "@folder:template/config-screen";

const LayoutSearch = () => (
  <Stack screenOptions={getDefaultStackScreenOptions()}>
    <Stack.Screen name="index"       options={{ title: "통합 검색" }} />
    <Stack.Screen name="users"       options={{ title: "사용자 검색" }} />
    <Stack.Screen name="communities" options={{ title: "포럼 검색" }} />
    <Stack.Screen name="posts"       options={{ title: "게시글 검색" }} />
  </Stack>
);

export default LayoutSearch;

검색 페이지

// app/(shell)/search/index.tsx
import ScreenSearch from "@folder:screen/search/screen-search";
export default () => <ScreenSearch />;

// app/(shell)/search/users.tsx
import ScreenSearchUsers from "@folder:screen/search/screen-search-users";
export default () => <ScreenSearchUsers />;

// app/(shell)/search/communities.tsx
import ScreenSearchCommunities from "@folder:screen/search/screen-search-communities";
export default () => <ScreenSearchCommunities />;

// app/(shell)/search/posts.tsx
import ScreenSearchPosts from "@folder:screen/search/screen-search-posts";
export default () => <ScreenSearchPosts />;

app/(shell)/inbox/_layout.tsx

import { Stack } from "expo-router";
import getDefaultStackScreenOptions from "@folder:template/config-screen";

const LayoutInbox = () => (
  <Stack screenOptions={getDefaultStackScreenOptions()}>
    <Stack.Screen name="index" options={{ title: "Inbox" }} />
  </Stack>
);

export default LayoutInbox;

app/(shell)/inbox/index.tsx

import ScreenInbox from "@folder:screen/inbox/screen-inbox";
export default () => <ScreenInbox />;

app/(shell)/my/_layout.tsx

import { Stack } from "expo-router";
import getDefaultStackScreenOptions from "@folder:template/config-screen";

const LayoutMy = () => (
  <Stack screenOptions={getDefaultStackScreenOptions()}>
    <Stack.Screen name="index"        options={{ title: "마이 페이지" }} />
    <Stack.Screen name="my-posts"     options={{ title: "내 게시글" }} />
    <Stack.Screen name="my-comments"  options={{ title: "내 댓글" }} />
    <Stack.Screen name="saved"        options={{ title: "저장함" }} />
    <Stack.Screen name="drafts"       options={{ title: "임시글" }} />
  </Stack>
);

export default LayoutMy;

마이 페이지

// app/(shell)/my/index.tsx
import ScreenMy from "@folder:screen/my/screen-my";
export default () => <ScreenMy />;

// app/(shell)/my/my-posts.tsx
import ScreenMyPosts from "@folder:screen/my/screen-my-posts";
export default () => <ScreenMyPosts />;

// app/(shell)/my/my-comments.tsx
import ScreenMyComments from "@folder:screen/my/screen-my-comments";
export default () => <ScreenMyComments />;

// app/(shell)/my/saved.tsx
import ScreenSaved from "@folder:screen/my/screen-saved";
export default () => <ScreenSaved />;

// app/(shell)/my/drafts.tsx
import ScreenDrafts from "@folder:screen/my/screen-drafts";
export default () => <ScreenDrafts />;

5) (stack) — 풀스크린(정식 상세/작성/프로필/설정)

app/(stack)/_layout.tsx

import { Stack } from "expo-router";
const LayoutStackRoot = () => <Stack screenOptions={{ headerShown: false }} />;
export default LayoutStackRoot;

app/(stack)/f/[sub]/_layout.tsx

import { Stack } from "expo-router";
import getDefaultStackScreenOptions from "@folder:template/config-screen";

const LayoutStackSub = () => (
  <Stack screenOptions={getDefaultStackScreenOptions()}>
    <Stack.Screen name="submit/index"                          options={{ title: "글쓰기" }} />
    <Stack.Screen name="submit/preview"                        options={{ title: "미리보기" }} />
    <Stack.Screen name="comments/[post-id]/index"              options={{ title: "게시글" }} />
    <Stack.Screen name="comments/[post-id]/edit"               options={{ title: "글 수정" }} />
    <Stack.Screen name="comments/[post-id]/comment/[comment-id]" options={{ title: "댓글" }} />
    <Stack.Screen name="mod"                                   options={{ headerShown: false }} />
  </Stack>
);

export default LayoutStackSub;

작성/상세 라우트

// app/(stack)/f/[sub]/submit/index.tsx
import ScreenSubmit from "@folder:screen/post/screen-submit";
export default () => <ScreenSubmit />;

// app/(stack)/f/[sub]/submit/preview.tsx
import ScreenSubmitPreview from "@folder:screen/post/screen-submit-preview";
export default () => <ScreenSubmitPreview />;

// app/(stack)/f/[sub]/comments/[post-id]/index.tsx
import ScreenPostDetail from "@folder:screen/post/screen-post-detail";
export default () => <ScreenPostDetail />;

// app/(stack)/f/[sub]/comments/[post-id]/edit.tsx
import ScreenPostEdit from "@folder:screen/post/screen-post-edit";
export default () => <ScreenPostEdit />;

// app/(stack)/f/[sub]/comments/[post-id]/comment/[comment-id].tsx
import ScreenCommentPermalink from "@folder:screen/post/screen-comment-permalink";
export default () => <ScreenCommentPermalink />;

app/(stack)/f/[sub]/mod/_layout.tsx

import { Stack } from "expo-router";
import getDefaultStackScreenOptions from "@folder:template/config-screen";

const LayoutMod = () => (
  <Stack screenOptions={getDefaultStackScreenOptions()}>
    <Stack.Screen name="queue"    options={{ title: "신고 큐" }} />
    <Stack.Screen name="reports"  options={{ title: "레포트" }} />
    <Stack.Screen name="settings" options={{ title: "포럼 설정" }} />
  </Stack>
);

export default LayoutMod;

모더레이션 라우트

// app/(stack)/f/[sub]/mod/queue.tsx
import ScreenModQueue from "@folder:screen/mod/screen-mod-queue";
export default () => <ScreenModQueue />;

// app/(stack)/f/[sub]/mod/reports.tsx
import ScreenModReports from "@folder:screen/mod/screen-mod-reports";
export default () => <ScreenModReports />;

// app/(stack)/f/[sub]/mod/settings.tsx
import ScreenModSettings from "@folder:screen/mod/screen-mod-settings";
export default () => <ScreenModSettings />;

app/(stack)/u/[username]/_layout.tsx

import { Stack } from "expo-router";
import getDefaultStackScreenOptions from "@folder:template/config-screen";

const LayoutUser = () => (
  <Stack screenOptions={getDefaultStackScreenOptions()}>
    <Stack.Screen name="index"                 options={{ title: "프로필" }} />
    <Stack.Screen name="posts"                 options={{ title: "작성 글" }} />
    <Stack.Screen name="comments"              options={{ title: "작성 댓글" }} />
    <Stack.Screen name="relations/followers"   options={{ title: "팔로워" }} />
    <Stack.Screen name="relations/following"   options={{ title: "팔로잉" }} />
  </Stack>
);

export default LayoutUser;

유저 라우트

// app/(stack)/u/[username]/index.tsx
import ScreenUserProfile from "@folder:screen/user/screen-user-profile";
export default () => <ScreenUserProfile />;

// app/(stack)/u/[username]/posts.tsx
import ScreenUserPosts from "@folder:screen/user/screen-user-posts";
export default () => <ScreenUserPosts />;

// app/(stack)/u/[username]/comments.tsx
import ScreenUserComments from "@folder:screen/user/screen-user-comments";
export default () => <ScreenUserComments />;

// app/(stack)/u/[username]/relations/followers.tsx
import ScreenFollowers from "@folder:screen/user/screen-followers";
export default () => <ScreenFollowers />;

// app/(stack)/u/[username]/relations/following.tsx
import ScreenFollowing from "@folder:screen/user/screen-following";
export default () => <ScreenFollowing />;

app/(stack)/settings/_layout.tsx

import { Stack } from "expo-router";
import getDefaultStackScreenOptions from "@folder:template/config-screen";

const LayoutSettings = () => (
  <Stack screenOptions={getDefaultStackScreenOptions()}>
    <Stack.Screen name="index"         options={{ title: "설정" }} />
    <Stack.Screen name="profile"       options={{ title: "프로필" }} />
    <Stack.Screen name="account"       options={{ title: "계정" }} />
    <Stack.Screen name="privacy"       options={{ title: "프라이버시" }} />
    <Stack.Screen name="notifications" options={{ title: "알림" }} />
    <Stack.Screen name="appearance"    options={{ title: "외양" }} />
    <Stack.Screen name="data/export"   options={{ title: "데이터 내보내기" }} />
    <Stack.Screen name="data/delete"   options={{ title: "데이터 삭제" }} />
  </Stack>
);

export default LayoutSettings;

설정 라우트

// app/(stack)/settings/index.tsx
import ScreenSettings from "@folder:screen/settings/screen-settings";
export default () => <ScreenSettings />;

// app/(stack)/settings/profile.tsx
import ScreenSettingsProfile from "@folder:screen/settings/screen-settings-profile";
export default () => <ScreenSettingsProfile />;

// app/(stack)/settings/account.tsx
import ScreenSettingsAccount from "@folder:screen/settings/screen-settings-account";
export default () => <ScreenSettingsAccount />;

// app/(stack)/settings/privacy.tsx
import ScreenSettingsPrivacy from "@folder:screen/settings/screen-settings-privacy";
export default () => <ScreenSettingsPrivacy />;

// app/(stack)/settings/notifications.tsx
import ScreenSettingsNotifications from "@folder:screen/settings/screen-settings-notifications";
export default () => <ScreenSettingsNotifications />;

// app/(stack)/settings/appearance.tsx
import ScreenSettingsAppearance from "@folder:screen/settings/screen-settings-appearance";
export default () => <ScreenSettingsAppearance />;

// app/(stack)/settings/data/export.tsx
import ScreenDataExport from "@folder:screen/settings/screen-data-export";
export default () => <ScreenDataExport />;

// app/(stack)/settings/data/delete.tsx
import ScreenDataDelete from "@folder:screen/settings/screen-data-delete";
export default () => <ScreenDataDelete />;

6) (modal)

app/(modal)/_layout.tsx

import { Stack } from "expo-router";
import getDefaultStackScreenOptions from "@folder:template/config-screen";

const LayoutModal = () => (
  <Stack screenOptions={{ ...getDefaultStackScreenOptions(), presentation: "modal" }}>
    <Stack.Screen name="image-picker"              options={{ title: "이미지 선택" }} />
    <Stack.Screen name="share"                     options={{ title: "공유" }} />
    <Stack.Screen name="report"                    options={{ title: "신고" }} />
    <Stack.Screen name="user-preview/[username]"   options={{ title: "사용자 정보" }} />
  </Stack>
);

export default LayoutModal;

모달 라우트

// app/(modal)/image-picker.tsx
import ScreenImagePicker from "@folder:screen/modal/screen-image-picker";
export default () => <ScreenImagePicker />;

// app/(modal)/share.tsx
import ScreenShare from "@folder:screen/modal/screen-share";
export default () => <ScreenShare />;

// app/(modal)/report.tsx
import ScreenReport from "@folder:screen/modal/screen-report";
export default () => <ScreenReport />;

// app/(modal)/user-preview/[username].tsx
import ScreenUserPreview from "@folder:screen/modal/screen-user-preview";
export default () => <ScreenUserPreview />;

7) 단축 링크

app/p/[short-id].tsx

import { useEffect } from "react";
import { View, Text } from "react-native";
import { router, useLocalSearchParams } from "expo-router";

const ShortLink = () => {
  const { ["short-id"]: shortId } = useLocalSearchParams<{ "short-id": string }>();

  useEffect(() => {
    // TODO: shortId → 정식 경로 조회 후 교체
    router.replace("/(shell)");
  }, [shortId]);

  return (
    <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
      <Text>링크 이동 중…</Text>
    </View>
  );
};

export default ShortLink;

8) 페이지 파일은 “화면 컴포넌트만” 렌더

  • route 파일@folder:screen/...에서 가져온 화면 컴포넌트만 리턴.
  • 헤더 타이틀/옵션/프레젠테이션은 전부 **해당 _layout.tsx**에서 정의.
  • UI 프리미티브는 @folder:ui/*, 템플릿/공통 옵션은 @folder:template/*에서 import.

이 구조로 교체하면, 라우팅 옵션의 중앙집중/예측성이 확보되고, 화면 파일은 순수 UI에 집중할 수 있다.