[Chore] #79 - Next.js 전환 구조 정리 및 Vite 제거#89
Conversation
📝 WalkthroughWalkthroughThis PR executes a comprehensive migration from Vite to Next.js, replacing the build toolchain and adopting Next.js App Router, static export deployment to Changes
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 17
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/CONTRIBUTING.md (1)
44-59:⚠️ Potential issue | 🟡 MinorInconsistent
.envfile reference.Line 46 mentions "
.env파일 설정이 필요합니다" but line 50 instructs to create.env.local. For Next.js,.env.localis the correct choice for local development secrets. Consider updating line 46-48 to reference.env.localfor consistency.📝 Suggested fix
### 환경 변수 설정 (.env) -Firebase 연동을 위해 `.env` 파일 설정이 필요합니다. +Firebase 연동을 위해 `.env.local` 파일 설정이 필요합니다. -> `.env` 파일은 절대 GitHub에 커밋하지 마세요. `.gitignore`에 등록되어 있습니다. +> `.env.local` 파일은 절대 GitHub에 커밋하지 마세요. `.gitignore`에 등록되어 있습니다. 환경 변수 값은 **팀 노션**을 참고해서 프로젝트 루트에 `.env.local` 파일을 생성하세요.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/CONTRIBUTING.md` around lines 44 - 59, Update the CONTRIBUTING docs to consistently reference `.env.local` instead of `.env`: change the heading and the sentence "`.env` 파일 설정이 필요합니다" to mention `.env.local`, and ensure the warning about not committing env files and the example variables remain unchanged but clearly labeled as for `.env.local`; look for the existing text that mentions ".env" and the example block around NEXT_PUBLIC_FIREBASE_* to make these edits so the doc consistently guides Next.js developers to use `.env.local`.
🧹 Nitpick comments (4)
src/shared/ui/file-uploader/file-uploader.stories.tsx (1)
5-11: Consider adding acomponentreference to meta for better autodocs.The
metaobject doesn't specify acomponent, which means autodocs won't automatically generate prop tables. Since this file demonstrates multiple components (ThumbnailUploader,FileListItem,FileUploader), you might want to either:
- Split into separate story files per component, or
- Add a primary component reference
💡 Option: Add primary component for autodocs
const meta = { title: "Shared/UI/FileUploader", + component: ThumbnailUploader, parameters: { layout: "centered", }, tags: ["autodocs"], } satisfies Meta;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/file-uploader/file-uploader.stories.tsx` around lines 5 - 11, The meta object for the storybook file doesn't set a component, so autodocs can't auto-generate prop tables; update the meta object (named meta) to include a primary component reference (e.g., component: FileUploader) or split the stories into separate files and set each file's meta.component to its respective exported component (ThumbnailUploader, FileListItem, FileUploader) so Storybook autodocs can produce proper prop tables.src/shared/ui/form/form.stories.tsx (1)
82-86:onUploadcallback signature mismatch.Per
ThumbnailUploaderProps(infile-uploader.tsx:15-20),onUploadreceives aFileparameter:onUpload: (file: File) => void. The current implementation ignores this parameter. While acceptable for a demo story, consider using the file to generate a proper preview URL for better demonstration fidelity.💡 Suggested improvement for realistic preview
<ThumbnailUploader previewUrl={preview} - onUpload={() => setPreview("https://picsum.photos/800/400")} + onUpload={(file) => setPreview(URL.createObjectURL(file))} onRemove={() => setPreview(undefined)} />Note: If using
URL.createObjectURL, remember to revoke the URL on remove to avoid memory leaks in real implementations.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/form/form.stories.tsx` around lines 82 - 86, The story's ThumbnailUploader usage ignores the File parameter expected by ThumbnailUploaderProps; update the onUpload prop passed to ThumbnailUploader to accept the File argument (e.g., onUpload={(file) => ...}) and generate a preview with URL.createObjectURL(file) instead of a fixed URL, storing that value via setPreview; also update onRemove to revoke the object URL (URL.revokeObjectURL) when clearing the preview to avoid a memory leak, referencing ThumbnailUploader, onUpload, setPreview and the ThumbnailUploaderProps signature in file-uploader.tsx.tsconfig.json (1)
58-66: Consider impact on IDE support for excluded files.Excluding
*.stories.ts(x)and*.test.ts(x)from the main tsconfig means these files won't get full TypeScript support from the Next.js plugin. This is typically fine since:
- Stories usually have their own tsconfig via Storybook
- Tests often use a separate config
However, if developers report IntelliSense issues in story/test files, you may need to add a
tsconfig.storybook.jsonthat extends this config but includes those patterns.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tsconfig.json` around lines 58 - 66, The tsconfig currently excludes story and test files via the "exclude" array entries ("src/**/*.stories.ts", "src/**/*.stories.tsx", "src/**/*.test.ts", "src/**/*.test.tsx", "src/**/*.spec.ts", "src/**/*..spec.tsx"), which can remove IDE/Next.js TypeScript support for those files; create a companion tsconfig (e.g., tsconfig.storybook.json) that extends the base tsconfig but overrides the "exclude" to omit those story/test globs so editors and Storybook get proper IntelliSense, or alternatively remove only the specific entries if you want the base config to include them—update the project setup docs to note the new tsconfig for Storybook/tests.package.json (1)
8-8: Clarify the--webpackflag usage in build script.The
next build --webpackflag is unusual. As of Next.js 15, Turbopack is available for production builds. The--webpackflag explicitly opts out of Turbopack. If this is intentional (e.g., for stability or compatibility reasons), consider adding a comment in the documentation. Otherwise, you might want to use justnext buildand let Next.js use the default bundler.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` at line 8, The build script in package.json currently uses the explicit flag "next build --webpack" which forces webpack instead of the default bundler; decide whether to keep opting out of Turbopack or remove the flag: either remove "--webpack" from the "build" script (script key "build") so Next.js uses the default bundler, or if webpack is required, add a short comment in the repo docs/README explaining why "next build --webpack" is intentionally used for compatibility/stability and reference the "build" script name so reviewers know the reason.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@next-env.d.ts`:
- Line 3: The dev-only explicit import import "./.next/dev/types/routes.d.ts" in
next-env.d.ts should be removed because it references a non-existent,
build-generated file; delete that import line and rely on the existing tsconfig
includes (e.g. ".next/dev/types/**/*.ts" and ".next/types/**/*.ts") to provide
the types so CI/clean builds no longer fail due to the missing .next file.
In `@src/shared/config/firebase.ts`:
- Around line 6-8: Replace the dynamic env lookup in getPublicEnv by using
static NEXT_PUBLIC_FIREBASE_* references (e.g.
process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, etc.) instead of process.env[key];
update your .env keys from VITE_FIREBASE_* to NEXT_PUBLIC_FIREBASE_* so they
match; and add a validation block before calling initializeApp that checks each
required NEXT_PUBLIC_FIREBASE_* value and throws/returns an explicit error if
any are undefined to fail fast (reference getPublicEnv usage sites and the
initializeApp call to locate where to change).
In `@src/shared/ui/avatars/avatars.tsx`:
- Around line 114-125: The avatar currently renders a non-interactive <div> even
when onClick is provided, so replace the outer wrapper with a semantic
interactive element when clickable: if onClick exists render a <button
type="button"> using the same avatarClasses, onClick handler and props (instead
of the <div>), otherwise keep the non-interactive wrapper; ensure you reference
AvatarProps, getAvatarClasses and avatarClasses so the class/name logic is
preserved and avoid nesting additional interactive descendants when switching to
a button.
In `@src/shared/ui/checkbox/checkbox.stories.tsx`:
- Around line 45-50: The DisabledChecked Story is passing a controlled prop
(checked: true) without an onChange handler which triggers React's
controlled-input warning; update the DisabledChecked.args to include readOnly:
true (or alternatively provide an onChange) so the component is treated as a
static checked input and the warning is suppressed—modify the DisabledChecked
Story args to add readOnly: true alongside checked: true and disabled: true.
- Around line 18-21: The InteractiveCheckbox story initializes component state
from args using useState(args.checked || false) which only captures the initial
value and doesn't update when Storybook controls change; update the
InteractiveCheckbox component to sync its local state with changes to
args.checked (e.g., inside InteractiveCheckbox use an effect that watches
args.checked and calls setChecked(args.checked ?? false)) so the Checkbox prop
checked stays in sync with Storybook controls while still updating via the
onChange handler.
In `@src/shared/ui/checkbox/checkbox.tsx`:
- Around line 11-20: The wrapper always includes "cursor-pointer" which makes
disabled checkboxes appear clickable; update the class construction so cursor
classes are conditional based on the disabled prop instead of hardcoding in
containerBaseClasses: remove "cursor-pointer" from the static
containerBaseClasses and, inside the Checkbox component (where container classes
are assembled, also referenced around the other affected block at lines ~39-45),
append "cursor-pointer" when !disabled and "cursor-not-allowed" or no pointer
class when disabled so disabled checkboxes do not show the pointer cursor.
In `@src/shared/ui/file-uploader/file-uploader.tsx`:
- Around line 190-196: The file input and drop handlers (e.g., handleFileChange
and the drop handler around lines 205-210) forward files without validating
against the component props; update both handlers to filter the incoming
FileList by the component's accept prop (supporting MIME types and extensions)
and enforce the multiple prop (if multiple is false, only keep the first
accepted file), then call onFileSelect with the filtered array and clear the
input; ensure the same accept-filtering logic is shared or extracted to a small
helper (e.g., isAcceptedFile / filterAcceptedFiles) so both handleFileChange and
the drop handler use identical validation.
- Around line 62-67: The file input change handler (handleFileChange) currently
calls onUpload for any selected file; mirror the drag-and-drop image guard by
validating the file is an image before invoking onUpload: inside
handleFileChange, check the same predicate used in the drag-drop path (e.g.,
file.type.startsWith('image/') or the existing is-image check used in the drop
handler) and only call onUpload(file) when it passes; if it fails, skip calling
onUpload (optionally console.warn or surface a user error) and still reset
e.currentTarget.value = "" to allow re-selection.
- Around line 1-3: This module uses React hooks (useRef, useState) and
interactive handlers so it must be a Next.js Client Component; add the directive
"use client" as the very first line of
src/shared/ui/file-uploader/file-uploader.tsx (above the imports) so
functions/components in this file like the FileUploader component, and
references to useRef/useState and Button/UploadIcon are executed on the client.
In `@src/shared/ui/form/form.stories.tsx`:
- Line 35: Replace the deprecated String.prototype.substr usage when generating
the id in form.stories.tsx: in the expression that currently uses
Math.random().toString(36).substr(2, 9) (the id generation), switch to
String.prototype.substring (e.g. use substring(2, 11) to preserve the same
9-character slice) or use slice(2, 11); update the id assignment inside the
story to use Math.random().toString(36).substring(2, 11) so substr is no longer
used.
In `@src/shared/ui/form/form.tsx`:
- Around line 56-61: The required indicator in the FormLabel component is
visual-only; update the JSX in FormLabel to make the star accessible by adding
aria-hidden="true" to the visual <span> (the '*' marker) and append a
screen-reader-only text node reading "required" (e.g., a span with
visually-hidden/SR-only class) so assistive tech announces the requirement;
modify the FormLabel render (the const content in FormLabel) to include these
changes while keeping the existing required prop handling.
In `@src/shared/ui/lists/lists.tsx`:
- Line 145: The row-level click targets (the div using getListItemClasses(...)
with handleItemClick and the similar divs around lines 293-297) are not keyboard
accessible; update the implementation so that when onClick/onRowClick is
provided you either render a native interactive element (e.g., a <button> or <a>
with proper href) or enhance the divs by adding role="button", tabIndex={0}, and
keyDown handler that calls the same handler on Enter/Space; ensure
handleItemClick and any onRowClick callbacks are invoked from both onClick and
the keyboard handler, and preserve disabled behavior (do not focus or respond to
keyboard when isDisabled is true).
- Around line 147-166: The checkbox inputs in lists.tsx lack accessible names;
update the input elements used when hasCheckbox (and the header checkbox) to
include proper accessibility attributes: for row checkboxes in the list item
component add aria-labelledby pointing to the item/title element's id (ensure
the title cell has a stable id or generate one from the item id), and for the
header checkbox add aria-label="Select all" (or aria-labelledby to a header
label) so screen readers announce intent; modify the input in the block where
handleCheckClick/onCheck/isChecked/isDisabled are used and mirror the same
change in the other checkbox occurrences (lines referenced around the header and
other rows) to ensure every checkbox is labeled.
In `@src/shared/ui/modal/modal.stories.tsx`:
- Around line 22-31: The trigger rendering currently checks !propsIsOpen so a
controlled isOpen={false} still shows the internal open button; change that
logic to detect uncontrolled mode by checking propsIsOpen === undefined (or
typeof propsIsOpen === "undefined") instead. Update FilterModalExample to use
propsIsOpen === undefined when deciding to render the internal trigger and when
wiring setInternalIsOpen, and replace any other occurrences of !propsIsOpen in
the modal examples (the blocks that reference propsIsOpen/propsOnClose,
internalIsOpen, isOpen, onClose) with the uncontrolled-mode check so the
internal trigger only appears when the component is actually uncontrolled.
In `@src/shared/ui/modal/modal.tsx`:
- Around line 10-16: The Modal implementation must complete the accessibility
contract: ensure the dialog has a reliable label and proper focus management.
Update the Modal/ModalHeader flow so that Modal uses aria-labelledby referencing
either the public titleId prop (if provided) or a generated id produced when
ModalHeader renders a title, and ensure ModalHeader sets that id on the title
element; also ensure the Modal does not hardcode a generated id that may be
absent. Add focus management in Modal: when isOpen becomes true, move focus into
the dialog (e.g., the dialog container or first focusable element), trap focus
inside while open, and restore focus to the previously focused element on close;
call onClose on Escape and clicks outside as before. You can implement this by
enhancing the Modal component (and ModalHeader) or by switching to a vetted
dialog primitive, but make sure to reference Modal, ModalHeader, titleId,
isOpen, and onClose when making the changes.
- Around line 117-124: The close button in the Modal component (the conditional
button rendered when onClose is provided in src/shared/ui/modal/modal.tsx) lacks
an explicit type and will act as type="submit" inside forms; update that button
element to include type="button" so clicking it does not trigger form submission
and only calls onClose (reference the JSX button with onClick={onClose} and
XIcon).
- Around line 84-106: The modal currently calls createPortal(..., document.body)
even during server render which can throw because document is undefined; update
the Modal component to track client mount using useState(false) and useEffect(()
=> setMounted(true), []), return null (or the non-portal render) while mounted
is false or if !isOpen, and only call createPortal when mounted is true and
isOpen is true; update references around isOpen, createPortal and document.body
to guard portal rendering by checking the new mounted state.
---
Outside diff comments:
In `@docs/CONTRIBUTING.md`:
- Around line 44-59: Update the CONTRIBUTING docs to consistently reference
`.env.local` instead of `.env`: change the heading and the sentence "`.env` 파일
설정이 필요합니다" to mention `.env.local`, and ensure the warning about not committing
env files and the example variables remain unchanged but clearly labeled as for
`.env.local`; look for the existing text that mentions ".env" and the example
block around NEXT_PUBLIC_FIREBASE_* to make these edits so the doc consistently
guides Next.js developers to use `.env.local`.
---
Nitpick comments:
In `@package.json`:
- Line 8: The build script in package.json currently uses the explicit flag
"next build --webpack" which forces webpack instead of the default bundler;
decide whether to keep opting out of Turbopack or remove the flag: either remove
"--webpack" from the "build" script (script key "build") so Next.js uses the
default bundler, or if webpack is required, add a short comment in the repo
docs/README explaining why "next build --webpack" is intentionally used for
compatibility/stability and reference the "build" script name so reviewers know
the reason.
In `@src/shared/ui/file-uploader/file-uploader.stories.tsx`:
- Around line 5-11: The meta object for the storybook file doesn't set a
component, so autodocs can't auto-generate prop tables; update the meta object
(named meta) to include a primary component reference (e.g., component:
FileUploader) or split the stories into separate files and set each file's
meta.component to its respective exported component (ThumbnailUploader,
FileListItem, FileUploader) so Storybook autodocs can produce proper prop
tables.
In `@src/shared/ui/form/form.stories.tsx`:
- Around line 82-86: The story's ThumbnailUploader usage ignores the File
parameter expected by ThumbnailUploaderProps; update the onUpload prop passed to
ThumbnailUploader to accept the File argument (e.g., onUpload={(file) => ...})
and generate a preview with URL.createObjectURL(file) instead of a fixed URL,
storing that value via setPreview; also update onRemove to revoke the object URL
(URL.revokeObjectURL) when clearing the preview to avoid a memory leak,
referencing ThumbnailUploader, onUpload, setPreview and the
ThumbnailUploaderProps signature in file-uploader.tsx.
In `@tsconfig.json`:
- Around line 58-66: The tsconfig currently excludes story and test files via
the "exclude" array entries ("src/**/*.stories.ts", "src/**/*.stories.tsx",
"src/**/*.test.ts", "src/**/*.test.tsx", "src/**/*.spec.ts",
"src/**/*..spec.tsx"), which can remove IDE/Next.js TypeScript support for those
files; create a companion tsconfig (e.g., tsconfig.storybook.json) that extends
the base tsconfig but overrides the "exclude" to omit those story/test globs so
editors and Storybook get proper IntelliSense, or alternatively remove only the
specific entries if you want the base config to include them—update the project
setup docs to note the new tsconfig for Storybook/tests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c505d809-305a-4d68-9f7b-dfa406a6737b
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (69)
.gitignore.storybook/main.ts.storybook/preview.ts.storybook/vitest.setup.tsREADME.mddocs/CONTRIBUTING.mddocs/VERSIONS.mddocs/seo-nextjs-migration-plan.mdeslint.config.jsindex.htmlnext-env.d.tsnext.config.tspackage.jsonpublic/index.htmlsrc/app/(auth)/login/page.tsxsrc/app/globals.csssrc/app/index.tsxsrc/app/layout.tsxsrc/app/page.tsxsrc/entities/user/index.tssrc/features/auth/index.tssrc/firebase.jssrc/main.tsxsrc/shared/config/firebase.tssrc/shared/ui/avatars/avatars.stories.tsxsrc/shared/ui/avatars/avatars.tsxsrc/shared/ui/badge/badge.stories.tsxsrc/shared/ui/badge/status-badge.stories.tsxsrc/shared/ui/button/button.stories.tsxsrc/shared/ui/card/card.stories.tsxsrc/shared/ui/checkbox/checkbox.stories.tsxsrc/shared/ui/checkbox/checkbox.tsxsrc/shared/ui/checkbox/index.tssrc/shared/ui/dropdown/dropdown.stories.tsxsrc/shared/ui/empty_states/empty-states.stories.tsxsrc/shared/ui/empty_states/empty-states.tsxsrc/shared/ui/file-uploader/file-uploader.stories.tsxsrc/shared/ui/file-uploader/file-uploader.tsxsrc/shared/ui/file-uploader/index.tssrc/shared/ui/form/form.stories.tsxsrc/shared/ui/form/form.tsxsrc/shared/ui/form/index.tssrc/shared/ui/icons/index.tsxsrc/shared/ui/inputBox/inputBox.stories.tsxsrc/shared/ui/inputBox/inputBox.tsxsrc/shared/ui/lists/lists.stories.tsxsrc/shared/ui/lists/lists.tsxsrc/shared/ui/modal/index.tssrc/shared/ui/modal/modal.stories.tsxsrc/shared/ui/modal/modal.tsxsrc/shared/ui/navigation/navigation.stories.tsxsrc/shared/ui/navigation/navigation.tsxsrc/shared/ui/pagination/pagination.stories.tsxsrc/shared/ui/spinner/spinner.stories.tsxsrc/shared/ui/tabs/tabs.stories.tsxsrc/shared/ui/tabs/tabs.tsxsrc/shared/ui/tag/tag.stories.tsxsrc/views/home/home.stories.tsxsrc/views/home/home.tsxsrc/views/home/index.tssrc/views/index.tssrc/views/login/index.tsxsrc/views/login/login.module.csstailwind.config.jstsconfig.app.jsontsconfig.jsontsconfig.node.jsonvite.config.tsvitest.shims.d.ts
💤 Files with no reviewable changes (7)
- .storybook/vitest.setup.ts
- vitest.shims.d.ts
- index.html
- src/main.tsx
- src/firebase.js
- public/index.html
- vite.config.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@firebase.json`:
- Around line 8-11: Add a new Next.js app-level not-found route component by
creating src/app/not-found.tsx that exports a default NotFound component (export
default function NotFound()) which renders a simple 404 UI; ensure the file
exists in the build so the static export (with trailingSlash: true) generates
out/404.html and route folders like out/login/index.html after running the next
build.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 7aa8ebc8-d3ad-4fd0-a88d-2710a2ef186c
📒 Files selected for processing (11)
.gitignoreREADME.mddocs/CONTRIBUTING.mddocs/VERSIONS.mddocs/page-rendering-strategy.mddocs/seo-nextjs-migration-plan.mdfirebase.jsonnext.config.tspackage.jsonpublic/index.htmlsrc/app/page.tsx
💤 Files with no reviewable changes (1)
- public/index.html
✅ Files skipped from review due to trivial changes (4)
- .gitignore
- docs/page-rendering-strategy.md
- docs/seo-nextjs-migration-plan.md
- docs/CONTRIBUTING.md
🚧 Files skipped from review as they are similar to previous changes (5)
- src/app/page.tsx
- next.config.ts
- README.md
- package.json
- docs/VERSIONS.md
There was a problem hiding this comment.
🧹 Nitpick comments (1)
docs/VERSIONS.md (1)
73-86: 업데이트 이력 섹션의 배치/정렬 규칙을 하나로 통일하면 좋겠습니다.
2026-03-29블록이## 업데이트 이력헤더 바깥에 있어, 이력 탐색 시 혼동이 생깁니다. 전부 동일 섹션 아래 역순(최신→과거)으로 정리하는 것을 권장합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/VERSIONS.md` around lines 73 - 86, Move the "### 2026-03-29" release block so it sits under the existing "## 업데이트 이력" header and ensure all dated blocks (e.g., "### 2026-03-29" and "### 2026-04-01 — Next.js 전환 1단계 문서 정리") are consistently ordered in reverse chronological order (latest → past); verify heading levels remain "##" for the section and "###" for each entry and adjust spacing/markdown separators so the entire update history is a single, consistently sorted section.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@docs/VERSIONS.md`:
- Around line 73-86: Move the "### 2026-03-29" release block so it sits under
the existing "## 업데이트 이력" header and ensure all dated blocks (e.g., "###
2026-03-29" and "### 2026-04-01 — Next.js 전환 1단계 문서 정리") are consistently
ordered in reverse chronological order (latest → past); verify heading levels
remain "##" for the section and "###" for each entry and adjust spacing/markdown
separators so the entire update history is a single, consistently sorted
section.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d0c06b0c-fa82-42d9-b995-ebba7227cad3
📒 Files selected for processing (7)
docs/CONTRIBUTING.mddocs/VERSIONS.mddocs/seo-nextjs-migration-plan.mdnext-env.d.tssrc/app/globals.csssrc/app/index.csssrc/app/index.tsx
💤 Files with no reviewable changes (2)
- src/app/index.css
- src/app/index.tsx
✅ Files skipped from review due to trivial changes (3)
- src/app/globals.css
- next-env.d.ts
- docs/seo-nextjs-migration-plan.md
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/CONTRIBUTING.md
- out/ 빌드 아티팩트 git 추적 제거 (git rm --cached) - inputBox: Tailwind JIT 오작동 유발 백틱 제거 - modal: 모듈 레벨 전역 변수 제거 → 클로저 캡처 방식으로 교체, titleId dead prop 제거 - navigation: <a> → next/link, <img> → next/image - next.config: allowedDevOrigins IP 하드코딩 → env 분리, images.unoptimized 추가 - views/index.ts: 잘못된 주석 수정 (Pages → Views)
- next-env.d.ts: .next/dev/types → .next/types (표준 경로) - docs/VERSIONS.md: tiptap 패키지 및 문서 갱신 규칙 포함한 HEAD 기준 유지 - pnpm-lock.yaml: remote 기준 채택 후 pnpm install 재조정
머지 후 remote에서 재추적된 out/ 디렉토리 파일 제거 .gitignore에 이미 명시되어 있으나 이전에 커밋된 파일 정리 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (1)
src/shared/ui/modal/modal.tsx (1)
56-58:⚠️ Potential issue | 🟠 MajorAccessibility contract is still incomplete (label target + focus handling).
Line 107 still always points
aria-labelledbyto a generated id, but that id is only meaningful when a titledModalHeaderactually renders. Also, the publicModalProps.titleId(Line 17) is not wired inModal(Line 56), and focus is still not moved/trapped/restored for keyboard users.Also applies to: 94-99, 105-108
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/shared/ui/modal/modal.tsx` around lines 56 - 58, The Modal component currently always sets aria-labelledby to a generated id and never uses the public ModalProps.titleId, and it doesn't manage focus; update Modal to accept and prefer ModalProps.titleId (fallback to the generated useId value only if no prop supplied), only apply aria-labelledby when a ModalHeader renders (e.g., via presence of children with role/header or by accepting a hasHeader flag), and implement keyboard focus management: on open move focus into the dialog (to the first focusable element or the dialog container), trap focus while open, and on close restore focus to the previously focused element; reference ModalProps.titleId, Modal, ModalHeader, titleId, aria-labelledby, and useId when making these changes.
🧹 Nitpick comments (2)
out/_next/static/chunks/app/(auth)/login/page-a3951dbd4039757d.js (1)
1-1: Please confirmout/is intentionally versioned.This is a generated, hash-named Next.js client chunk. Keeping
out/_next/static/*in git will add a lot of churn and can easily desync the checked-in export fromsrc/appwhen someone forgets to rebuild. If deployment already performs the build/export, I'd removeout/from version control; otherwise document that the checked-in export is the deployable artifact.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@out/_next/static/chunks/app/`(auth)/login/page-a3951dbd4039757d.js at line 1, The PR has a generated Next.js client chunk committed under the exported site (the out/ directory, e.g. files under out/_next/static/*) which should not be versioned; either remove out/ from git, add out/ (or out/_next/static/) to .gitignore, and commit the removal, or if you intend to treat the checked-in export as the deployable artifact, document that clearly (README/CONTRIBUTING) and add CI validation to ensure the exported files are rebuilt and in sync; update the repo root .gitignore and project docs accordingly and remove the generated chunk file(s) from the commit if you choose to unversion them.package.json (1)
21-21: Pin the Node.js runtime floor for Next.js 16.Next.js 16 requires Node 20.9+, but this repository does not specify a minimum Node version in
package.json(noenginesfield) or via.nvmrc, Volta, or CI configuration. Contributors on Node 18 or early Node 20 will only discover incompatibility when runningnext devornext build. Add anenginesfield topackage.json:"engines": { "node": ">=20.9.0" }Alternatively, pin the version with
.nvmrcorVoltatooling.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@package.json` at line 21, Add a Node runtime floor so contributors running Next.js 16 don't hit runtime incompatibilities: update the project configuration to declare a minimum Node version (for example add an "engines" field in package.json with node >=20.9.0) or alternatively add a .nvmrc or Volta config pin; ensure the change references the package.json manifest (where "next": "^16.2.1" is declared) so CI and contributors are informed of the Node >=20.9.0 requirement.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@out/_next/static/chunks/app/`(auth)/login/page-a3951dbd4039757d.js:
- Line 1: Tabs and input icon buttons remove native focus outlines (you can find
the tab button class generator inside function B and the input icon buttons
inside component C where buttons use "focus:outline-none"/"outline-none"), so
restore visible keyboard focus by removing the global "outline-none" /
"focus:outline-none" and adding explicit focus-visible styles (e.g.
focus-visible:ring-2 focus-visible:ring-offset-2
focus-visible:ring-[var(--color-primary-500,`#3385DB`)] or a matching
focus-visible shadow class) to those elements' class lists so keyboard users get
a clear focus indicator for the tab buttons (B) and the input icon buttons
(password toggle and clear buttons in C).
- Line 1: The LoginPage export (symbol S / LoginPage) currently renders dead UI
— wire real auth handlers in src/app/(auth)/login/page.tsx: implement
handleGoogleSignIn (call your auth provider/signIn flow) and hook it to the
Google button's onClick, implement handleSubmitCredentials to call your
credential auth API or signIn and replace the form's noop onSubmit with this
handler (symbols: form onSubmit, input component C, submit Button n), change the
signup anchor href from "#signup" to the real signup route or router.push
(replace the anchor with a navigation that uses handleSignupNavigate), and pass
real callbacks into Navigation (symbol L) for
onLogin/onSignup/onLogout/onAdminToggle/onProfileClick/onAdminDashboardClick;
rebuild so the module exports the working LoginPage.
In `@out/index.html`:
- Line 1: The root page currently returns null and only calls
router.replace("/login") inside useEffect (src/app/page.tsx), which yields an
empty HTML shell if the client JS is delayed; update page.tsx so the component
renders a minimal static fallback UI (e.g., visible text and a plain anchor/link
to "/login" or a message like "Redirecting to /login...") instead of null, or
alternatively move the redirect out of the client component into the
hosting/server layer; ensure the change is made where useEffect calls
router.replace and where the component currently returns null so the
server-rendered HTML contains a usable non-JS fallback.
In `@package.json`:
- Line 39: package.json added eslint-config-next but eslint.config.js never
includes it; update eslint.config.js by importing or requiring
"eslint-config-next" and spreading it into the top-level extends array alongside
"@eslint/js", "plugin:`@typescript-eslint/recommended`", and
"plugin:react-hooks/recommended" so Next.js lint rules are applied; locate the
extends array and add the eslint-config-next entry (or spread the imported
config) to the array used by the exported config.
---
Duplicate comments:
In `@src/shared/ui/modal/modal.tsx`:
- Around line 56-58: The Modal component currently always sets aria-labelledby
to a generated id and never uses the public ModalProps.titleId, and it doesn't
manage focus; update Modal to accept and prefer ModalProps.titleId (fallback to
the generated useId value only if no prop supplied), only apply aria-labelledby
when a ModalHeader renders (e.g., via presence of children with role/header or
by accepting a hasHeader flag), and implement keyboard focus management: on open
move focus into the dialog (to the first focusable element or the dialog
container), trap focus while open, and on close restore focus to the previously
focused element; reference ModalProps.titleId, Modal, ModalHeader, titleId,
aria-labelledby, and useId when making these changes.
---
Nitpick comments:
In `@out/_next/static/chunks/app/`(auth)/login/page-a3951dbd4039757d.js:
- Line 1: The PR has a generated Next.js client chunk committed under the
exported site (the out/ directory, e.g. files under out/_next/static/*) which
should not be versioned; either remove out/ from git, add out/ (or
out/_next/static/) to .gitignore, and commit the removal, or if you intend to
treat the checked-in export as the deployable artifact, document that clearly
(README/CONTRIBUTING) and add CI validation to ensure the exported files are
rebuilt and in sync; update the repo root .gitignore and project docs
accordingly and remove the generated chunk file(s) from the commit if you choose
to unversion them.
In `@package.json`:
- Line 21: Add a Node runtime floor so contributors running Next.js 16 don't hit
runtime incompatibilities: update the project configuration to declare a minimum
Node version (for example add an "engines" field in package.json with node
>=20.9.0) or alternatively add a .nvmrc or Volta config pin; ensure the change
references the package.json manifest (where "next": "^16.2.1" is declared) so CI
and contributors are informed of the Node >=20.9.0 requirement.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2ee1c45c-9ee3-42dc-b435-6266c9e83ec9
⛔ Files ignored due to path filters (3)
out/assets/ajou-logo.svgis excluded by!**/*.svgout/vite.svgis excluded by!**/*.svgpnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (50)
.gitignoreREADME.mddocs/VERSIONS.mdout/404.htmlout/404/index.htmlout/__next.__PAGE__.txtout/__next._full.txtout/__next._head.txtout/__next._index.txtout/__next._tree.txtout/_next/static/chunks/1f9827ce-f7a0a51790a77a5e.jsout/_next/static/chunks/842-9dfc9af168e60700.jsout/_next/static/chunks/app/(auth)/login/page-a3951dbd4039757d.jsout/_next/static/chunks/app/_global-error/page-b55364013c5cdff5.jsout/_next/static/chunks/app/_not-found/page-c46e016a44b33e7a.jsout/_next/static/chunks/app/layout-2bd09d57c7f734b0.jsout/_next/static/chunks/app/page-d84632cbe95311f8.jsout/_next/static/chunks/framework-d6dd631488a05ecd.jsout/_next/static/chunks/main-app-713d12a8f2ea8b88.jsout/_next/static/chunks/main-fff885e1cc206200.jsout/_next/static/chunks/polyfills-42372ed130431b0a.jsout/_next/static/chunks/webpack-282e1267345f1a63.jsout/_next/static/css/729202eae8491218.cssout/_next/static/css/a031dbc3d4ab787a.cssout/_next/static/wJA63Lu7_pm4CosKUSIC_/_buildManifest.jsout/_next/static/wJA63Lu7_pm4CosKUSIC_/_ssgManifest.jsout/_not-found/__next._full.txtout/_not-found/__next._head.txtout/_not-found/__next._index.txtout/_not-found/__next._not-found.__PAGE__.txtout/_not-found/__next._not-found.txtout/_not-found/__next._tree.txtout/_not-found/index.htmlout/_not-found/index.txtout/index.htmlout/index.txtout/login/__next.!KGF1dGgp.login.__PAGE__.txtout/login/__next.!KGF1dGgp.login.txtout/login/__next.!KGF1dGgp.txtout/login/__next._full.txtout/login/__next._head.txtout/login/__next._index.txtout/login/__next._tree.txtout/login/index.htmlout/login/index.txtpackage.jsonsrc/shared/config/firebase.tssrc/shared/ui/checkbox/checkbox.stories.tsxsrc/shared/ui/modal/modal.stories.tsxsrc/shared/ui/modal/modal.tsx
✅ Files skipped from review due to trivial changes (33)
- .gitignore
- out/login/__next._tree.txt
- out/__next._tree.txt
- out/_next/static/chunks/app/page-d84632cbe95311f8.js
- out/login/__next._head.txt
- out/_not-found/__next._head.txt
- out/_not-found/__next._not-found.txt
- out/_not-found/__next._tree.txt
- out/_not-found/index.html
- out/next/static/wJA63Lu7_pm4CosKUSIC/_ssgManifest.js
- out/_not-found/__next._full.txt
- out/login/__next.!KGF1dGgp.login.PAGE.txt
- out/login/__next.!KGF1dGgp.login.txt
- out/_not-found/__next._not-found.PAGE.txt
- out/__next._index.txt
- out/404/index.html
- out/_not-found/__next._index.txt
- out/index.txt
- out/__next._full.txt
- out/_next/static/chunks/app/layout-2bd09d57c7f734b0.js
- out/__next._head.txt
- out/login/__next._index.txt
- out/404.html
- out/login/index.html
- out/_next/static/chunks/app/_global-error/page-b55364013c5cdff5.js
- out/next/static/wJA63Lu7_pm4CosKUSIC/_buildManifest.js
- out/_next/static/chunks/webpack-282e1267345f1a63.js
- out/_next/static/chunks/app/_not-found/page-c46e016a44b33e7a.js
- out/__next.PAGE.txt
- out/login/index.txt
- out/login/__next.!KGF1dGgp.txt
- out/login/__next._full.txt
- out/_next/static/chunks/main-app-713d12a8f2ea8b88.js
🚧 Files skipped from review as they are similar to previous changes (5)
- src/shared/config/firebase.ts
- src/shared/ui/checkbox/checkbox.stories.tsx
- README.md
- docs/VERSIONS.md
- src/shared/ui/modal/modal.stories.tsx
| "@vitest/coverage-v8": "^4.0.17", | ||
| "autoprefixer": "^10.4.24", | ||
| "eslint": "^9.39.2", | ||
| "eslint-config-next": "^16.2.1", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' eslint.config.js
echo
rg -n --glob 'eslint.config.*' 'eslint-config-next|@next/eslint-plugin-next|@next/next|core-web-vitals' .Repository: ajou-industry-matching/aim-frontend
Length of output: 642
Wire eslint-config-next into the config array.
package.json adds eslint-config-next, but eslint.config.js only extends @eslint/js, typescript-eslint, and react-hooks (lines 10-12). The eslint-config-next import and spread into the extends array are missing, which means Next.js linting rules won't run.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@package.json` at line 39, package.json added eslint-config-next but
eslint.config.js never includes it; update eslint.config.js by importing or
requiring "eslint-config-next" and spreading it into the top-level extends array
alongside "@eslint/js", "plugin:`@typescript-eslint/recommended`", and
"plugin:react-hooks/recommended" so Next.js lint rules are applied; locate the
extends array and add the eslint-config-next entry (or spread the imported
config) to the array used by the exported config.
There was a problem hiding this comment.
Pull request overview
Vite 기반 SPA 잔재를 제거하고 Next.js(App Router) 중심으로 프로젝트 구조/의존성/문서를 정리해, Firebase Hosting 정적 export(out/) 배포 기준을 명확히 하는 PR입니다.
Changes:
- Vite 엔트리/설정 제거 및 Next.js(App Router) 엔트리(
src/app) 도입 +/→/login진입 흐름 정리 - Storybook을
@storybook/nextjs로 전환하고 스토리 타입 import/패키지 버전을 정렬 - FSD 레이어 정리(
pages→views), 클라이언트 컴포넌트"use client"명시, Firebase env 검증 및 문서 갱신
Reviewed changes
Copilot reviewed 54 out of 63 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| vitest.shims.d.ts | Vitest 브라우저 타입 참조 제거 |
| vite.config.ts | Vite 설정 제거 |
| tsconfig.node.json | Node(tsc) 대상 파일을 next.config.ts로 전환 |
| tsconfig.json | Next.js(App Router) 기준 TS 설정/alias로 재정의 |
| tsconfig.app.json | Vite client types 제거 및 @views/* 경로 반영 |
| tailwind.config.js | Vite index.html 기반 content 경로 제거 |
| src/views/login/login.module.css | 로그인 패널 애니메이션을 CSS Module로 전환 |
| src/views/login/index.tsx | LoginPage를 client component로 설정, CSS Module 적용 |
| src/views/index.ts | views 배럴 export 추가 |
| src/views/home/index.ts | HomePage export 추가 |
| src/views/home/home.tsx | Storybook용 HomePage 추가 |
| src/views/home/home.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/tag/tag.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/tabs/tabs.tsx | "use client" 추가 |
| src/shared/ui/tabs/tabs.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/spinner/spinner.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/pagination/pagination.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/navigation/navigation.tsx | "use client" 추가 |
| src/shared/ui/navigation/navigation.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/modal/modal.tsx | "use client" + hydration 안전 isMounted 패턴 + button type 보강 |
| src/shared/ui/modal/modal.stories.tsx | Storybook 프레임워크 import 전환 + controlled/uncontrolled 예제 정리 |
| src/shared/ui/lists/lists.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/inputBox/inputBox.tsx | "use client" 추가 |
| src/shared/ui/inputBox/inputBox.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/index.ts | UI 배럴 export 재정리(Input를 새 경로로 export 등) |
| src/shared/ui/icons/index.tsx | 공용 아이콘 export 구성/정렬 업데이트 |
| src/shared/ui/form/form.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/file-uploader/file-uploader.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/empty_states/empty-states.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/dropdown/dropdown.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/checkbox/checkbox.stories.tsx | useArgs 기반 인터랙티브 스토리로 전환 |
| src/shared/ui/card/card.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/button/button.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/badge/status-badge.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/badge/badge.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/ui/avatars/avatars.stories.tsx | Storybook 프레임워크 import를 Next로 전환 |
| src/shared/config/firebase.ts | NEXT_PUBLIC_FIREBASE_* 기반 env 검증 및 설정 적용 |
| src/main.tsx | Vite SPA 엔트리 제거 |
| src/firebase.js | Vite env 기반 Firebase 초기화 파일 제거 |
| src/features/auth/index.ts | 빈 export로 정리 |
| src/entities/user/index.ts | 빈 export로 정리 |
| src/app/page.tsx | / 진입 시 /login CSR 리다이렉트 추가 |
| src/app/layout.tsx | Next root layout + metadata 추가 |
| src/app/index.tsx | 레거시 App 엔트리 제거 |
| src/app/index.css | 레거시 App 엔트리 스타일 제거 |
| src/app/globals.css | Next 글로벌 스타일 진입점에서 src/index.css import |
| src/app/(auth)/login/page.tsx | App Router /login 라우트에서 views LoginPage 렌더 |
| README.md | Vite → Next.js로 스택/명령어/문서 링크 갱신 |
| public/index.html | Firebase Hosting 기본 fallback 페이지 제거 |
| package.json | Next dev/build 중심 스크립트/의존성으로 전환, Storybook 버전 정렬 |
| next.config.ts | static export(output: export) 등 Next 설정 추가 |
| next-env.d.ts | Next 타입 참조 파일 추가/수정 |
| index.html | Vite index.html 엔트리 제거 |
| firebase.json | Hosting public을 out/으로 전환, SPA rewrite 제거 |
| eslint.config.js | .next, storybook-static ignore 및 Vite 관련 린트 설정 제거 |
| docs/VERSIONS.md | 현재 스택/버전/운영 기준 문서 갱신 |
| docs/page-rendering-strategy.md | SSG/CSR 중심 렌더링 전략 문서 추가 |
| docs/CONTRIBUTING.md | Next.js 전환 기준/구조/실행/배포 가이드 갱신 |
| .storybook/vitest.setup.ts | Storybook+Vitest setup 제거 |
| .storybook/preview.ts | @storybook/nextjs 기반 Preview 타입으로 전환 |
| .storybook/main.ts | @storybook/nextjs framework로 전환 |
| .gitignore | .next, out, .vercel ignore 추가 |
| type RootLayoutProps = Readonly<{ | ||
| children: React.ReactNode; | ||
| }>; | ||
|
|
||
| export default function RootLayout({ children }: RootLayoutProps): React.ReactElement { | ||
| return ( |
There was a problem hiding this comment.
RootLayoutProps/RootLayout reference React.ReactNode and React.ReactElement, but React isn’t imported in this module. With TS modules + jsx: react-jsx, this will typically fail typechecking (Cannot find name 'React'). Import the needed types from react (or avoid the explicit React.* annotations).
| import { LoginPage } from "@/views/login"; | ||
|
|
||
| export default function LoginRoute(): React.ReactElement { |
There was a problem hiding this comment.
This file returns React.ReactElement but doesn’t import React (or ReactElement) anywhere. That will usually fail TypeScript compilation in module scope. Import type ReactElement from react (or drop the return type annotation).
| import { LoginPage } from "@/views/login"; | |
| export default function LoginRoute(): React.ReactElement { | |
| import type { ReactElement } from "react"; | |
| import { LoginPage } from "@/views/login"; | |
| export default function LoginRoute(): ReactElement { |
| "dev": "next dev", | ||
| "build": "next build --webpack", | ||
| "start": "python3 -m http.server 3000 -d out", | ||
| "lint": "eslint .", | ||
| "preview": "vite preview", | ||
| "preview": "python3 -m http.server 3000 -d out", |
There was a problem hiding this comment.
start/preview scripts depend on python3 being installed on every dev/CI environment. If the team needs cross-platform scripts, consider using a Node-based static server (e.g. npx serve out -l 3000) or a dedicated package (http-server) instead of relying on Python.
🔎 What is this PR?
📝 Changes
📚 Background / Context
✔ Checklist
pnpm build)pnpm lint)Summary by CodeRabbit
Release Notes
New Features
Build & Deployment
out/directory.Developer Experience