diff --git a/src/hooks/useApi.tsx b/src/hooks/useApi.tsx
new file mode 100644
index 00000000..e683bc07
--- /dev/null
+++ b/src/hooks/useApi.tsx
@@ -0,0 +1,41 @@
+import { useEffect, useState, useCallback } from 'react';
+
+type ApiResponse = {
+ data: T | null;
+ isLoading: boolean;
+ error: string | null;
+ makeRequest: (params: P) => Promise;
+};
+
+export function useApi(
+ fetchFunction: (params: P) => Promise,
+ params: P
+): ApiResponse {
+ const [data, setData] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ const wrappedFunction = useCallback(
+ async (params: P) => {
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ const result = await fetchFunction(params);
+ setData(result);
+ } catch (err) {
+ console.error(err);
+ setError('데이터 로딩 실패');
+ } finally {
+ setIsLoading(false);
+ }
+ },
+ [fetchFunction]
+ );
+
+ useEffect(() => {
+ wrappedFunction(params);
+ }, [JSON.stringify(params), wrappedFunction]);
+
+ return { data, isLoading, error, makeRequest: wrappedFunction };
+}
diff --git a/src/hooks/useBoards.ts b/src/hooks/useBoards.ts
new file mode 100644
index 00000000..4bc1c3b0
--- /dev/null
+++ b/src/hooks/useBoards.ts
@@ -0,0 +1,74 @@
+import { useEffect, useRef, useState } from 'react';
+import { getBoards } from '@/src/apis/boardsApi';
+import type { Board } from '@/src/apis/boardTypes';
+
+const PAGE_SIZE = 10;
+
+export const useBoards = (orderBy: string) => {
+ const [boards, setBoards] = useState([]);
+ const [isFetching, setIsFetching] = useState(false);
+ const [hasMore, setHasMore] = useState(true);
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+ const [page, setPage] = useState(1);
+ const observerRef = useRef(null);
+
+ const fetchBoards = async () => {
+ setIsLoading(true);
+ setError(null);
+ try {
+ const response = await getBoards({
+ page,
+ pageSize: PAGE_SIZE,
+ orderBy,
+ });
+ setBoards((prevBoards) => [...prevBoards, ...response.list]);
+ setIsFetching(false);
+
+ if (response.list.length < PAGE_SIZE) {
+ setHasMore(false);
+ }
+ } catch (err) {
+ setError('Failed to fetch boards');
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ useEffect(() => {
+ fetchBoards();
+ }, [page, orderBy]);
+
+ useEffect(() => {
+ const observer = new IntersectionObserver(
+ (entries) => {
+ if (entries[0].isIntersecting && !isFetching && hasMore) {
+ setIsFetching(true);
+ setPage((prevPage) => prevPage + 1);
+ }
+ },
+ { threshold: 0.5 }
+ );
+
+ const current = observerRef.current;
+ if (current) observer.observe(current);
+
+ return () => {
+ if (current) observer.unobserve(current);
+ };
+ }, [isFetching, hasMore]);
+
+ const resetBoards = () => {
+ setBoards([]);
+ setPage(1);
+ setHasMore(true);
+ };
+
+ return {
+ boards,
+ isLoading,
+ error,
+ observerRef,
+ resetBoards,
+ };
+};
diff --git a/styles/globals.css b/styles/globals.css
index 14b366ae..4e36ccef 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -29,22 +29,3 @@ header {
footer {
background-color: var(--Secondary-900);
}
-
-.max-container {
- max-width: var(--size-max-width);
- margin: auto;
-}
-
-/* tablet */
-@media screen and (max-width: 1199px) {
- .max-container {
- max-width: 760px;
- }
-}
-
-/* mobile */
-@media screen and (max-width: 767px) {
- .max-container {
- max-width: 360px;
- }
-}