1. SSG(Static Site Generation) + Headless CMS로 제작해볼까
동아리 온보딩 페이지를 제작 중이다. CSR로 제작할까, SSR로 제작할까 고민 중이었는데, SSG(Static Site Generation)을 사용하면 좋다는 의견이 있어서 Next.js로 SSG를 구현하기로 했다. 왜냐하면 동아리 온보딩 페이지는 내용이 거의 바뀌지 않으니까.
그래도 컨텐츠를 업데이트해야할 수도 있다. 그럴 때마다 코드를 수정해서 배포하면 되지 않을까 생각했는데, 코드를 수정하지 않고도 컨텐츠를 업데이트할 수 있는, 콘텐츠 관리만을 위한 서비스인 Headless CMS를 활용하면 된다! 어떤 Headless CMS를 활용할까 생각하다가, strapi가 좋다는 사람들의 의견이 많아서 strapi를 사용해보기로 했다.
strapi를 사용하려면 현재 작업 중인 프론트엔드 디렉토리와는 다른 디렉토리에서 strapi를 설치해야 한다.
자세한 설치법은 밑의 링크를 참고하자
설치법 링크: https://velog.io/@onestone/오픈소스-Headless-CMS-Strapi-설치-및-사용법
2. Strapi에 데이터를 추가해보자
커리큘럼 페이지를 만들었고, 커리큘럼 내용을 strapi에 만들고 연결해보려한다.
일단 먼저 strapi 관리자 패널에 접속하자. 나는 처음에 production 패널로 들어가서 왜 수정이 안되나 당황했었다.
명령어를 “npm run start”가 아닌 “npm run develop”을 입력하고 http://localhost:1337/admin으로 접속하자.
그리고 이제 왼쪽 사이드바에서 “Coontent-Type Builder” → “Create new collection type”을 클릭했다.
컬렉션 이름은 Curriculum으로 입력하고, 나는 다음과 같은 필드를 추가했다.
처음에는 content의 type을 ‘Rich text’로 설정해줬는데 . Strapi의 Rich text 필드가 객체 형태로 반환되어 오류가 발생했다. 그래시 content의 타입을 다시 ‘Text’로 바꾸고 데이터들을 일일이 수정하는 과정을 거쳤다.
3. Strapi 권한 설정
그리고 이제 권한 설정을 해야한다.
a. 왼쪽 사이드바에서 "Settings" -> "Roles" -> "Public"을 클릭한다.
b. "Curriculum" 섹션을 찾아 "find" 와 "findOne" 권한에 체크한다.
c. 페이지 하단의 "Save"를 클릭한다.
4. Strapi CORS 설정
그리고 데이터 입력을 해주고, CORS 설정을 따로 해줘야한다.
CORS 설정은 strapi 디렉토리에서 config/middlewares.js에서 다음과 같이 적어줬다.
module.exports = [
"strapi::errors",
{
name: "strapi::security",
config: {
contentSecurityPolicy: {
useDefaults: true,
directives: {
"connect-src": ["'self'", "https:"],
"img-src": [
"'self'",
"data:",
"blob:",
"<https://market-assets.strapi.io>",
],
"media-src": ["'self'", "data:", "blob:"],
upgradeInsecureRequests: null,
},
},
},
},
{
name: "strapi::cors",
config: {
enabled: true,
header: "*",
origin: [
"",
"",
"",
],
},
},
"strapi::poweredBy",
"strapi::logger",
"strapi::query",
"strapi::body",
"strapi::session",
"strapi::favicon",
"strapi::public",
];
5. Strapi API 토큰 생성 후 코드 작성
그 다음 API 토큰을 생성했는데, read-only로 설정해주고 발급을 받았다.
그리고 .env.local 파일을 디렉토리 상위에 생성해주고 다음과 같이 입력했다.
참고로 나는 처음에 STRAPI_API_TOKEN이라고 생성해줬는데, 자꾸 오류가 발생했다. 앞에 ‘NEXT_PUBLIC’을 붙여줘야한다!
그리고 api.ts를 생성해서 다음과 같이 코드를 넣어줬다.
import axios from 'axios';
const API_URL =
process.env.NEXT_PUBLIC_STRAPI_API_URL || '<http://localhost:1337>';
const API_TOKEN = process.env.NEXT_PUBLIC_STRAPI_API_TOKEN;
export interface CurriculumItem {
id: number;
attributes: {
part: string;
week: number;
content: string;
};
}
export async function fetchCurriculum(part: string): Promise<CurriculumItem[]> {
console.log('fetchCurriculum called with part:', part);
try {
const response = await axios.get(`${API_URL}/api/curricula`, {
params: {
filters: {
part: {
$eq: part,
},
},
sort: 'week:asc',
},
headers: {
Authorization: `Bearer ${API_TOKEN}`,
},
});
console.log('API response received: ', response.data);
if (response.data && Array.isArray(response.data.data)) {
console.log('Returning curriculum data:', response.data.data);
return response.data.data;
} else {
console.error('Unexpected API response structure:', response.data);
return [];
}
} catch (error) {
console.error('Error in fetchCurriculum:', error);
if (axios.isAxiosError(error)) {
console.error('Axios error details:', error.response?.data);
}
throw error;
}
}
에러가 자꾸 발생해서, console.log를 통해서 데이터가 잘 전달되는지 확인했다.
그리고 데이터를 받는 CurriculumCarousel 컴포넌트에는 다음과 같이 입력했다.
const [currentWeek, setCurrentWeek] = useState(0);
const [direction, setDirection] = useState(0);
const [curriculum, setCurriculum] = useState<CurriculumItem[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const loadCurriculum = async () => {
try {
setLoading(true);
setError(null);
const data = await fetchCurriculum(part);
console.log('Fetched curriculum data:', data);
setCurriculum(data);
} catch (err) {
console.error('Error loading curriculum:', err);
setError('Failed to load curriculum. Please try again.');
} finally {
setLoading(false);
}
};
loadCurriculum();
}, [part]);
const totalWeeks = curriculum.length;
(...)
if (loading) {
return <div className="text-white">Loading curriculum...</div>;
}
if (error) {
return <div className="text-white">Error: {error}</div>;
}
if (curriculum.length === 0) {
return <div className="text-white">커리큘럼 데이터에 접근이 안돼요</div>;
}
const currentCurriculum = curriculum[currentWeek];
(...)
<h4 className="text-lg font-semibold mb-2">
{currentCurriculum?.attributes.week}주차
</h4>
<p className="text-center">
{currentCurriculum?.attributes.content}
</p>
(...)
6. Strapi 데이터들의 STATE를 Published로 바꿔줘야한다
콘솔에서 네트워크 탭을 통해 확인해보니, 200 OK로 서버로부터 데이터가 정상적으로 받아와졌다. 그런데 문제는, 빈 배열이 온다는 것이었다! 코드 상 문제는 없는 거 같은데..
문제는 strapi에서 데이터들의 state를 draft에서 published로 바꿔줘야한다는 것이었다!
published로 바꿔줘야 데이터가 제대로 전달이 되는 것이었다. 이 사실을 몰라서 꽤 애를 먹었었다.
7. 로컬에서 이제 연결은 잘 되는데
이제 제대로 데이터가 잘 받아온다! 지금은 로컬에서 strapi와 프론트엔드 프로젝트를 실행해서 서로 연결했었는데, 이제 배포하는 사이트에 적용시키려면 어떻게 해야할까? 하루종일 컴퓨터를 켜놓을 수도 없고. 아마 비용적인 문제 때문에 데이터를 직접 코드로 입력하게 되지 않을까하는 생각이 들었다. 고민을 조금 더 해봐야겠다.
'KUIT' 카테고리의 다른 글
[kuit_onboarding] 풀페이지 스크롤 애니메이션 구현하기(Framer-motion) (1) | 2024.08.23 |
---|---|
[KUIT] 2024-1 웹 파트장 후기 (29) | 2024.06.06 |
[KUIT] 10주차 보충 - 토큰 저장 위치, 세션 인증 방식과 JWT, .env 파일 (0) | 2024.06.06 |
[KUIT] 9주차 워크북 보충 - API endpoint, Axios와 Fetch의 차이, GraphQL (0) | 2024.06.05 |
[KUIT] 8주차 워크북 보충 - 전역 상태 관리 라이브러리 정리 (0) | 2024.06.05 |