룰루랄라 내 홈페이지를 만들던 중에, 블로그의 최신글을 불러와서 보여주고 싶었다. 티스토리의 Open API가 있기를 바랬지만,, 없었고,, api를 확인해봤지만 SSR 쓰는 것으로 보여서 결국은 크롤링을 해야했다. 보통 크롤링은 보안때문에 브라우저에서 요청을 잘 안하지만, 하나를 위해서 백엔드 서버를 배포하는게 싫었다. 서버 사이드에서 요청을 하면 cors를 안띄우는걸 알았지만, 개인적으로 Next.js를 아직 그렇게 선호하지 않아서 마이그레이션하기는 불-편했다. 그러다가 그냥 새로 파서 api 하나만 만들어놓기로 했다. 사실 진짜 가능할지 몰랐다.. 이걸 다이나믹 라우팅으로 배포해놔도 될거같은데 그럼 티스토리가 싫어하려나? 이게 문제가 되는지, 어디까지 가능한건지 모르겠다! 일단 이왕인거 블로그를 SSR로 서브도메인으로 갈아탈 예정
Next.js 프로젝트 설치하기
$ pnpx create-next-app@latest
Next.js 프로젝트에서 api 만들기
Next.js는 폴더 구조를 기반으로 아우팅을 하고 있고, api 폴더를 미리 라우팅용으로 정해놨기 때문에 api 폴더 아래에 원하는 엔드포인트 구조를 만들고 route.ts 파일을 만들어주면 된다.
cheerio를 설치하여 서버에서 크롤링을 하고, 원하는 요소를 선택하여 응답값으로 보내주기만 하면 된다.
import * as cheerio from "cheerio";
import { NextResponse } from "next/server";
import axios from "axios";
interface PostType {
title: string;
url?: string;
}
export async function GET() {
try {
const response = await axios.get("https://a-honey.tistory.com");
const html = response.data;
const $ = cheerio.load(html);
const posts: PostType[] = [];
$(".article-content").each((index, element) => {
const title = $(element).find(".title").text();
const url = $(element).find("a").attr("data-tiara-click_url");
posts.push({ title, url });
});
return NextResponse.json(posts);
} catch (error) {
return NextResponse.json(
{ message: "Error fetching posts" },
{ status: 500 }
);
}
}
http://localhost:3000/api/tistory/latest-posts 등 설정한 api 경로로 들어가면 다음과 같은 응답값을 반환한 것을 확인할 수 있다.
Next.js 프로젝트 배포하기
해당 프로젝트를 배포하기만 하면 다른 프로젝트에서도 api를 사용할 수 있다.
ahyeonjung-ssr.vercel.app/api/tistory/latest-posts
배포한 API 사용하기
배포한 이후에는 그냥 api와 똑같은 방식으로 사용하면 된다.
const Posts = () => {
const [posts, setPosts] = useState<PostType[]>([]);
useEffect(() => {
const fetchLatestPosts = async () => {
try {
const response = axios.get(
"https://ahyeonjung-ssr.vercel.app/api/tistory/latest-posts"
);
setPosts(response || []);
} catch (e) {
console.log(e);
}
};
fetchLatestPosts();
}, []);
return (
<div className="flex-column gap-[20px]">
<div>Recent posts</div>
<div className="grid grid-cols-2 gap-[20px]">
<PostItem title={posts[0].title} url={posts[0].url} />
<PostItem title={posts[1].title} url={posts[1].url} />
<PostItem title={posts[2].title} url={posts[2].url} />
<PostItem title={posts[3].title} url={posts[3].url} />
</div>
</div>
);
};
export default Posts;
당연히 새로 배포한 api용 Next.js를 기존의 프로젝트에서 요청하면 CORS 에러가 뜰거라 생각해서, 서브 도메인으로 설정하려고 했었다. 하지만 따로 에러를 안주길래 알아보니 기본이 모든 도메인을 허용하는 것이고, 특정 도메인만 허용하기 위해서는 Access-Control-Allow-Origin 설정을 추가로 넣어주면 된다고 한다.
캐시 해제하기
게시글을 새로 발행해도 가져오지 못하는 문제가 있었다. 브라우저에 캐싱되는 거라고 생각했는데 다른 브라우저에서도 똑같이 작동했다. 페이지를 캐시하듯이 api도 캐시하는 듯? no-cache를 통해 vercel이 캐시하지 못하게 설정해준다. 그냥 짧게만 해줘도 된다.
export async function GET() {
try {
const response = await axios.get("https://a-honey.tistory.com");
const html = response.data;
const $ = cheerio.load(html);
const posts: PostType[] = [];
$(".article-content").each((index, element) => {
const title = $(element).find(".title").text();
const url = $(element).find("a").attr("data-tiara-click_url");
posts.push({ title, url });
});
const nextResponse = NextResponse.json(posts);
nextResponse.headers.set("Cache-Control", "no-store, no-cache, must-revalidate, proxy-revalidate");
nextResponse.headers.set("Pragma", "no-cache");
nextResponse.headers.set("Expires", "0");
return nextResponse;
} catch (error) {
return NextResponse.json(
{ message: "Error fetching posts" },
{ status: 500 }
);
}
}