이전 글에서 썼던 방법은 navbar에 있는 내용은 미리 prerender되면서 react.js만 사용했을때 보다는 나은 seo를 제공하지만 여전히 본문의 데이터는 페이지 소스보기에서 확인할 수 없었기에 seo에 좋지 않았다. 이를 해결해보자.
서버단에서 데이터를 완성시키고 props로 넘겨준다.
이제 API_KEY도 별도로 숨길 필요가 없다.
여기에서는 아까 rewrites로 마스킹해주었던 url을 사용할 수 없다.
그런데, 문제가 있다. 이렇게 되면 데이터를 불러오는 시간동안 클라이언트는 빈 화면을 보게 된다. 그래서 우리는 결정해야 한다. 사용자에게 일부 ui를 보면서 로딩 중에 데이터를 받아올 것인가, 처음부터 완성된 ui를 보여줄 것인가!
index.jsx
import { useState, useEffect } from 'react';
import axios from 'axios';
import Seo from '../components/Seo';
export default function Home({ movies }) {
return (
<div>
<Seo title={'Home'} />
<ul>
{movies.map(movie => (
<li key={movie.id}>
<h1>{movie.title}</h1>
<p>{movie.overview}</p>
</li>
))}
</ul>
</div>
);
}
export async function getServerSideProps() {
const API_KEY = '9c1da7e439a125227bcd28cdc4e7b02a';
const { data } = await axios.get(`https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}`);
return {
props: {
movies: data.results,
},
};
}
router.push를 이용해서 index.js에서 받아온 movies 정보를 기반으로 상세 페이지에 query로 담아서 정보를 보내줄 수 있다. 이렇게 했을 때 /movies/1232 이런 식으로 해당 페이지에 직접 들어오면 페이지를 확인할 수 없지만 홈페이지(index.js)를 통해서 특정 상세페이지에 들어가게 된다면 정상적으로 데이터가 나온다. 이렇게 하면 로딩 시간을 없애서 사용성 향상을 가져올 수 있다.
pages/index.jsx
import { useState, useEffect } from 'react';
import axios from 'axios';
import Seo from '../components/Seo';
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Home({ movies }) {
const router = useRouter();
const handleClick = (id, title) => {
// a. Link처럼 페이지 이동만 구현 했을 때
// router.push(`/movies/${id}`);
// b. query로 데이터 담아서 보내기
router.push(
{
pathname: `/movies/${id}`,
query: {
id,
title,
},
},
`/movies/${id}`
);
};
return (
<div>
<Seo title={'Home'} />
<ul>
{movies.map(movie => (
<li key={movie.id} onClick={() => handleClick(movie.id, movie.title)}>
<Link href={`movies/${movie.id}`}>
<a>
<h1>{movie.title}</h1>
<p>{movie.overview}</p>
</a>
</Link>
</li>
))}
</ul>
</div>
);
}
export async function getServerSideProps() {
const API_KEY = '9c1da7e439a125227bcd28cdc4e7b02a';
const { data } = await axios.get(`https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}`);
return {
props: {
movies: data.results,
},
};
}
pages/movies/[id].jsx
import React from 'react';
import { useRouter } from 'next/router';
export default function Detail() {
const router = useRouter();
const { title } = router.query;
return (
<>
<h2>{title || 'Loading'}</h2>
</>
);
}
url에 movies/spider-man/12/3/ 이런 식으로 뒤에 계속 path가 붙을 경우를 처리해보자.
우선 파일명을 [id].jsx
로 지었던 것을 [...params].jsx
로 바꾼다. id를 params로 바꾼 이유는 url에서 여러 경로를 가져올것이라서 id라는 의미는 어울리지 않기 때문이다.
이제 router를 console찍어보면 이제 query가 string이 아니고 객체가 들어있는 것을 볼 수 있다.
그 객체 안에는 파일이름으로 지었던 params가 들어있고 그 params는 배열로 되어있다.
client단에서 router처리를 해줄지 server단에서 pre-render하는 과정에 router처리를 해줄지 정할 수 있다. 아래 예제의 경우에는 index.jsx에서 query로 title을 보내주고 있기 때문에 만약에 title만 받는 경우라고 한다면 새로 api를 받아올 필요없이 server에서 router를 이용하여 미리 렌더링을 하는 것이 좋다. 별도의 로딩 걸리는 시간이 없을 것으로 예상되기 때문이다.
index.jsx
import { useState, useEffect } from 'react';
import axios from 'axios';
import Seo from '../components/Seo';
import Link from 'next/link';
import { useRouter } from 'next/router';
export default function Home({ movies }) {
const router = useRouter();
const handleClick = (id, title) => {
router.push(`/movies/${title}/${id}`);
};
return (
<div>
<Seo title={'Home'} />
<ul>
{movies.map(movie => (
<li key={movie.id} onClick={() => handleClick(movie.id, movie.title)}>
<Link href={`/movies/${movie.original_title}/${movie.id}`}>
<a>
<h1>{movie.title}</h1>
<p>{movie.overview}</p>
</a>
</Link>
</li>
))}
</ul>
</div>
);
}
export async function getServerSideProps() {
const API_KEY = '9c1da7e439a125227bcd28cdc4e7b02a';
const { data } = await axios.get(`https://api.themoviedb.org/3/movie/popular?api_key=${API_KEY}`);
return {
props: {
movies: data.results,
},
};
}
[...params].jsx
import React from 'react';
import { useRouter } from 'next/router';
import Seo from '../../components/Seo';
export default function Detail({ title, id }) {
// const router = useRouter();
// const [title, id] = router.query.params || []; // 새로고침을 하거나 해당 페이지로 직접 접속하면 에러가 뜬다. 이 페이지는 백엔드에서 pre-render되기 때문이다. 이 때 server에는 router.query.params 배열이 아직 존재하지 않는다. 그래서 에러가 뜨는 것이다. 그래서 이 때 일단 배열을 줘서 백엔드에서 오류를 제거한다.
return (
<>
<Seo title={title} />
<h2>{title}</h2>
</>
);
}
// title이 페이지 소스에 나타나지 않는데 완전 SEO를 위해서 최적화를 한다고 하면 또 다시 getServerSideProps를 사용한다.
// 새로 api로 데이터를 받아오는 것이 아니라서 loading이 없더라도 페이지가 곧 바로 뜰 가능성이 높고 SEO까지 챙길 수 있어서 좋다.
export function getServerSideProps({ params: { params } }) {
// const [title, id] = ctx.params.params;
const [title, id] = params;
return {
props: {
title,
id,
},
};
}