diff --git a/.eslintrc.json b/.eslintrc.json
index 5131d7f3..3b470e2d 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -29,9 +29,11 @@
"no-unused-vars": "off",
"import/no-duplicates": "error",
"unused-imports/no-unused-imports": "error",
- "@typescript-eslint/no-explicit-any": "warn",
"@typescript-eslint/no-unused-vars": "warn",
"simple-import-sort/exports": "warn",
+ "react/display-name": "off",
+ "@next/next/no-img-element": "off",
+ "@typescript-eslint/no-explicit-any": "warn",
"simple-import-sort/imports": [
"warn",
{
diff --git a/.gitignore b/.gitignore
index 69a2cbc6..a3062fe6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
# dependencies
.env
+.env.production
/node_modules
/.pnp
.pnp.js
diff --git a/generate-runtime-config.mjs b/generate-runtime-config.mjs
new file mode 100755
index 00000000..a1ec225b
--- /dev/null
+++ b/generate-runtime-config.mjs
@@ -0,0 +1,22 @@
+import fs from 'fs';
+import path from 'path';
+
+// 환경 변수를 읽어들임
+const runtime = process.env.NODE_ENV === 'production' ? 'edge' : 'nodejs';
+
+const configContent = `export const runtime = '${runtime}';\n`;
+
+const configDir = path.join(process.cwd(), 'src/config');
+const configPath = path.join(configDir, 'runtime.js');
+
+// 디렉토리가 존재하지 않으면 생성
+if (!fs.existsSync(configDir)) {
+ fs.mkdirSync(configDir, { recursive: true });
+}
+
+// runtime.js 파일 생성
+fs.writeFileSync(configPath, configContent, 'utf8');
+
+console.log(`Runtime configuration written to ${configPath}`);
+
+
diff --git a/package.json b/package.json
index 3027ecbb..afea9990 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
"private": true,
"scripts": {
"dev": "next dev",
+ "prebuild": "node generate-runtime-config.mjs",
"build": "next build",
"start": "next start",
"lint": "next lint",
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index c2e54ee8..2f2cda2d 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -10,12 +10,15 @@ import { cn } from "@shared/utils/cn";
import "./globals.css";
import MSWProviders from "@mocks/MSWProviders";
+import { Toaster } from "@shared/components/ui/toaster";
export const metadata: Metadata = {
title: "FEW",
description: "매일 아침마다 경제 아티클과 문제를 보내드려요!",
};
+// export const runtime = 'edge'; // TBD: 개발환경과 분리
+
const pretendard = localFont({
src: [
{
@@ -64,6 +67,7 @@ export default function RootLayout({
>
{children}
+
diff --git a/src/app/workbook/[id]/page.tsx b/src/app/workbook/[id]/page.tsx
index 35193112..5b6dcf95 100644
--- a/src/app/workbook/[id]/page.tsx
+++ b/src/app/workbook/[id]/page.tsx
@@ -1,4 +1,5 @@
"use client";
+
import Image from "next/image";
import { usePathname } from "next/navigation";
@@ -11,6 +12,7 @@ import { useToast } from "@shared/components/ui/use-toast";
import CurriculumSection from "@workbook/components/CurriculumSection";
import OverviewSection from "@workbook/components/OverviewSection";
import TitleSection from "@workbook/components/TitleSection";
+import WorkbookSkeleton from "@workbook/components/WorkbookSkeleton";
import { getWorkbookQueryOptions } from "@workbook/remotes/getWorkbookQueryOptions";
import { getWorkbookId } from "@workbook/utils";
@@ -51,7 +53,7 @@ export default function WorkbookPage() {
setIsClient(true);
}, []);
- if (isLoading) return
Loading...
;
+ if (isLoading) return ;
if (isError) return Error loading workbook
;
return (
@@ -66,7 +68,7 @@ export default function WorkbookPage() {
width={0}
height={0}
sizes="100vw"
- style={{ width: "100%", height: "auto" }}
+ style={{ width: "100%", height: "338px" }}
/>
{
+export function createQueryProviderWrapper () {
const queryClient = new QueryClient();
return ({ children }: { children: ReactNode }) => (
diff --git a/src/common/components/ContentSkeleton/index.tsx b/src/common/components/ContentSkeleton/index.tsx
new file mode 100644
index 00000000..cfd166c3
--- /dev/null
+++ b/src/common/components/ContentSkeleton/index.tsx
@@ -0,0 +1,10 @@
+import { Skeleton } from "@shared/components/ui/skeleton";
+
+export default function ContentSkeleton ({
+ className,
+ ...props
+ }: React.HTMLAttributes) {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/config/runtime.js b/src/config/runtime.js
new file mode 100644
index 00000000..203c5dbb
--- /dev/null
+++ b/src/config/runtime.js
@@ -0,0 +1 @@
+export const runtime = 'nodejs';
diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts
index ae62acb5..141827ba 100644
--- a/src/mocks/handlers.ts
+++ b/src/mocks/handlers.ts
@@ -1,8 +1,7 @@
import { http, HttpResponse } from "msw";
import { apiRoutes } from "@shared/constants/apiRoutes";
-
-import { getWorkbookId } from "@workbook/utils";
+import { _3_SECOND, delay } from "@shared/utils/delay";
import response from "./response";
@@ -31,19 +30,24 @@ export const submitAnswerHandler = http.post(
return HttpResponse.json(response[apiRoutes.submitAnswer]);
},
);
-export const workbookHandler = http.get(apiRoutes.workbook, ({ request, params }) => {
+export const workbookHandler = http.get(apiRoutes.workbook, async ({ request, params }) => {
const workbookId = params
- if (!workbookId) {
- return new HttpResponse(null, { status: 404 });
- }
+ if (!workbookId) {
+ return new HttpResponse(null, { status: 404 });
+ }
- console.log(
- HttpResponse.json(response[apiRoutes.workbook]),
- apiRoutes.workbook,
- );
- return HttpResponse.json(response[apiRoutes.workbook]);
-});
+ console.log(
+ HttpResponse.json(response[apiRoutes.workbook]),
+ apiRoutes.workbook,
+ );
+
+ // 딜레이 적용
+ await delay(_3_SECOND);
+
+ return HttpResponse.json(response[apiRoutes.workbook]);
+ },
+);
export const handlers = [
tagsHandler,
diff --git a/src/problem/components/TagList/TagList.stories.tsx b/src/problem/components/TagList/TagList.stories.tsx
index 2e35041c..ea2b6c98 100644
--- a/src/problem/components/TagList/TagList.stories.tsx
+++ b/src/problem/components/TagList/TagList.stories.tsx
@@ -1,5 +1,5 @@
import TagList from ".";
-import { tagsHandler } from "@mocks/worker";
+import { tagsHandler } from "@mocks/handlers";
import { Meta, StoryObj } from "@storybook/react";
const meta = {
diff --git a/src/shared/components/ui/skeleton.tsx b/src/shared/components/ui/skeleton.tsx
new file mode 100644
index 00000000..2f6eeacb
--- /dev/null
+++ b/src/shared/components/ui/skeleton.tsx
@@ -0,0 +1,15 @@
+import { cn } from "@shared/utils/cn"
+
+function Skeleton({
+ className,
+ ...props
+}: React.HTMLAttributes) {
+ return (
+
+ )
+}
+
+export { Skeleton }
diff --git a/src/shared/utils/delay.ts b/src/shared/utils/delay.ts
new file mode 100644
index 00000000..3bad933c
--- /dev/null
+++ b/src/shared/utils/delay.ts
@@ -0,0 +1,5 @@
+export const _1_SECOND = 1_000;
+export const _3_SECOND = 3_000;
+
+
+export const delay = (second = _3_SECOND) => new Promise((resolve) => setTimeout(resolve, second));
diff --git a/src/workbook/components/CurriculumSection/index.tsx b/src/workbook/components/CurriculumSection/index.tsx
index ff7f7913..7635731b 100644
--- a/src/workbook/components/CurriculumSection/index.tsx
+++ b/src/workbook/components/CurriculumSection/index.tsx
@@ -1,4 +1,5 @@
import { ICurriculumItem } from "@workbook/types";
+
import CurriculumItem from "../CurriculumItem";
interface CurriculumSectionProps {
diff --git a/src/workbook/components/TitleSection/index.tsx b/src/workbook/components/TitleSection/index.tsx
index 73650c17..b00eea7c 100644
--- a/src/workbook/components/TitleSection/index.tsx
+++ b/src/workbook/components/TitleSection/index.tsx
@@ -1,7 +1,8 @@
+import React from "react";
+
import { Writer } from "@workbook/types";
import ShareIcon from "public/assets/icon36/share_36.svg";
-import React from "react";
interface TitleSectionProps {
category: string;
diff --git a/src/workbook/components/WorkbookSkeleton/index.tsx b/src/workbook/components/WorkbookSkeleton/index.tsx
new file mode 100644
index 00000000..d7932411
--- /dev/null
+++ b/src/workbook/components/WorkbookSkeleton/index.tsx
@@ -0,0 +1,29 @@
+import { Skeleton } from "@shared/components/ui/skeleton";
+
+import ContentSkeleton from "@common/components/ContentSkeleton";
+
+export default function WorkbookSkeleton() {
+ const skeletonItems = new Array(6).fill(null);
+
+ return (
+
+
+
+ {/* Content Skeleton */}
+
+
+
+ {skeletonItems.map((_, index) => (
+
+ ))}
+
+
+ );
+}
diff --git a/src/workbook/remotes/getWorkbookQueryOptions.ts b/src/workbook/remotes/getWorkbookQueryOptions.ts
index 9803ac4d..7a8e715d 100644
--- a/src/workbook/remotes/getWorkbookQueryOptions.ts
+++ b/src/workbook/remotes/getWorkbookQueryOptions.ts
@@ -1,4 +1,4 @@
-import { UseQueryOptions, useQuery } from "@tanstack/react-query";
+import { useQuery,UseQueryOptions } from "@tanstack/react-query";
import { ApiResponse, axiosRequest } from "@api/api-config";