- Next.js 13을 이용해 이미지 갤러리를 만들어보았다.
- Next.js 문법을 간단히 익히는 것을 목표로 작업하였는데, 13 이전 버전의 코드가 섞여있는 부분이 살짝 있을 수 있다.
- Next JS 실습 강좌! React를 더욱 편리하게 SEO 를 참고하며 작업했다.
Next.js를 사용해야하는 이유는 SEO, SSR, CSR의 개념과 관련이 있다.
HTML이 클라이언트에서 렌더링 되는 방식이다.
클라이언트에서 처음 요청시 비어있는 HTML
에 모든 리소스들을 다운받고 추가 요청시 그부분만 데이터를 받아서 html 템플릿과 결합시켜 화면에 보여준다.
👍장점
사용자 경험
이 높아진다.필요한 데이터
만 요청하기 때문에 서버의 부담이 적음.👎단점
초기 로딩이 느리다.
컨텐츠가 비어있는 상태
이기 때문에 SEO에 취약하다.
HTML이 서버에서 렌더링 되는 방식이다. (여기서 서버라는 것은 Frontend 서버를 말한다)
클라이언트에서 요청을 하면 서버에서 데이터까지 갖춘 완성된 html
을 클라이언트에 전달해준다.
👍장점
SEO에 최적화
되어있다.👎단점
이미 컨텐츠가 구성되어 있는 HTML로 렌더링되기 때문에 검색 엔진에서 파악될 수 있다.
그렇기 때문에 검색결과에 더 높은 순위를 차지 할 수 있게 도와준다.
위의 SSR 과 CSR 의 차이를 알고 있어야 React에서 언제 Next.js를 적용해야 할 지 알 수 있다.
npx create-next-app 앱이름
npm run dev // 개발 서버 열기
npm run build // 빌드
npm run start // 빌드 서버 열기 (프로덕트 모드)
📂 public
📂 styles
📂 pages
📂 api
- index.js
- _app.js
- _document.js
page 폴더 아래 js파일들이 있다.
_document
와 _app
에는 페이지에 공통적으로 적용될 내용을 작성하곤 하는데, 두 파일의 용도에 차이가 있다.
_app
은 서버로 요청이 들어왔을 때 가장 먼저 실행되는 컴포넌트로, 페이지에 적용할 공통 레이아웃의 역할을 한다.
모든 컴포넌트에 공통으로 적용할 속성 관리
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
export default MyApp
_document
는 _app
다음에 실행되며, 공통적으로 활용할 (Ex. 메타 태그)나 태그 안에 들어갈 내용들을 커스텀할때 활용한다.
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
React에선 라우팅을 하기 위해선 <Route>
컴포넌트가 필요했었다.
하지만 Next.js는 파일 시스템 기반의 라우팅이 가능하기 때문에, pages 경로에 파일만 추가하면 자동적으로 route할 수 있다.
📂 pages 폴더 안에 js파일을 작성하면 Next.js가 알아서 라우팅을 해준다.
pages 아래의 파일명이 곧 url이 되는 것이다.
(예외적으로 index.js의 경우 /index
가 아닌 /
이 된다. 즉 기본 path다.)
📂 pages
- _app.js
- _document.js
- index.js
- detail.js
- photos.js
위 예시의 경우 세가지 라우팅이 가능하다.
index.js = /
detail.js = /detail
photos.js = /photos
📂 pages
- _app.js
- _document.js
📂 api
📂 photos
📂 [id]
- index.js
만약 /photos/1
, /photos/2
, /photos/3
... 과 같이 동적 라우팅을 하고 싶다면
Next.js 에서는 [param] 과 같이 페이지에 존재하는 컴포넌트 파일명에 괄호를 씌우는 것으로 가능하다.
그리고 그 폴더 안에는 index.js 파일이 반드시 있어야한다.
파일 안에 작성하는 코드는 React와 동일하다.
더 자세한 건 아래서 다루겠다.
Next에서 자체적으로 제공해주는 유용한 컴포넌트들이 있다.
Head라는 컴포넌트 안에 title과 meta 태그 작성할 수 있고, 페이지 별로 html의 head 태그 안 정보를 다르게 적용 가능하다.
import Head from "next/head";
const HeadInfo = ({ title, keyword, contents }) => {
return (
<Head>
<title>{title}</title>
<meta keyword={keyword} />
<meta contents={contents} />
</Head>
);
};
페이지 이동을 할 때 Link 컴포넌트를 사용해야한다.
Next.js의 Link 컴포넌트는 React router dom의 Link 와 거의 비슷한 것 같다.
props로 href (경로)
를 반드시 작성해야한다.
import Link from "next/link";
export default function Home() {
return (
<div>
<ul>
<li>
<Link href="/">index</Link>
</li>
<li>
<Link href="/test">test</Link>
</li>
</ul>
next.js 13 이전 버전에선 Link 컴포넌트 안에 a 태그를 반드시 사용해야한다.
하지만 13버전부턴 적지 않아도 된다.
보통 이미지는 HTML의 img 태그를 사용한다. 작은 크기의 웹페이지에서는 효율적일 수 있으나, 많은 양의 이미지를 다루는 페이지에서는 HTML의 img 태그의 로딩이 느릴 수 있다.
그래서 next.js에서는 “Image” 컴포넌트를 이용해 자동으로 이미지를 최적화를 해준다.
- 필수 속성 : src, width, height (반드시 넓이와 높이 지정해줘야함.)
- 선택 속성 : layout, loader, placeholder, priority, quality, sizes, blurDataURL, loading, objectFit, objectPosition, onLoadingComplete,style
Image 컴포넌트엔 다양한 props를 사용해 최적화를 할 수 있는데, 내용이 방대하여 관련 레퍼런스를 참고하였습니다.
import Image from 'next/image';
const Profile = () => {
return (
<>
<h1>User Profile</h1>
<Image
src='img.jpg'
alt="user profile picture"
width={300}
heifht={300}
/>
</>
)
}
Image src에 remote url 을 쓰려면 아래 NextConfig 설정이 필수이다. (Next.js의 보안 정책 때문이라고 한다.)
/** @type {import('next').NextConfig} */
module.exports = {
images: {
formats: ["image/avif", "image/webp"],
remotePatterns: [
{
protocol: "https",
hostname: "via.placeholder.com",
port: "",
pathname: "/150/**",
},
],
},
위 설정을 했는데도 오류가 난다면, image 컴포넌트의 src url뒤에 확장자가 빠져서 그럴 수도 있다. 확장자를 직접 써주면 해결된다. (ex .png)
<Image
src={`${photo.thumbnailUrl}.png`}
width={100}
height={100}
alt={photo.title}
/>
Next.js에서도 css는 일반적인 방식으로 import하여 사용할 수 있다.
하지만 나는 편의를 위해 CSS module과 Styled-component를 적용해보았다.
Next.js는 CSS 모듈도 지원한다.
CSS 파일 이름을 지을 때 아래의 네이밍 컨벤션에 따라 작성하면 된다.
[name].module.css
CSS 모듈 방식을 사용하면 알아서 유니크한 클래스명이 생성되어 CSS가 지역 스코프를 갖게 된다. 즉, 서로 다른 CSS 모듈 파일에서 동일한 클래스명을 짓더라도 그 둘이 충돌될 걱정을 하지 않아도 되는 것이다.
다음과 같은 css 파일이 있다면
.container {
background-color: red;
}
적용 할 땐 아래와 같이 적용하면 된다.
import Homestyles from './Home.module.css'
export function Home() {
return (
<div
className={Homestyles.container}
>
컨테이너
</div>
)
}
Next에서 style-component을 사용하려면 바벨 설정이 필요하다.
npm install babel-plugin-styled-components
또는 yarn add babel-plugin-styled-components
로 다운을 받고,
파일 .babelrc
를 생성한 뒤 아래 처럼 작성해주면 된다.
{
"presets": ["next/babel"],
"plugins": [
[
"styled-components",
{
"ssr": true,
"displayName": true,
"preprocess": true
}
]
]
}
Next.js에선 공통된 레이아웃을 적용할 때 레이아웃 컴포넌트
를 만들어 _app.js에 적용하면 된다.
📂components 폴더를 생성하고 필요한 컴포넌트를 생정한다.
📂 pages
📂 components
- Headinfo.js
- Layout.js
- Nav.js
import Head from "next/head";
const HeadInfo = ({ title, keyword, contents }) => {
return (
<Head>
<title>{title}</title>
<meta keyword={keyword} />
<meta contents={contents} />
</Head>
);
};
HeadInfo.defaultProps = {
title: "White Gallery",
keyword: "Gallery powered by next js",
contents: "practice next js",
};
export default HeadInfo;
Head 컴포넌트가 받아 사용할 props 3가지 title, keyword, contents
가 있고,
props가 없을 때 적용할 defaultProps도 지정해주었다.
모든 페이지에 적용 할 Navbar를 작성한다.
import Link from "next/link";
import navStyles from "../styles/Nav.module.css";
const Nav = () => {
return (
<nav className={navStyles.nav}>
<ul>
<li>
<Link href="/">Home</Link>
</li>
<li>
<Link href="/detail">detail</Link>
</li>
</ul>
</nav>
);
};
export default Nav;
Navbar를 적용한 Layout 컴포넌트를 생성한다.
import Nav from "./Nav";
import Head from "next/head";
const Layout = ({ children }) => {
return (
<div>
<Nav />
{children}
</div>
);
};
export default Layout;
_app.js에서 Layout 컴포넌트로 <Component {...pageProps} />를 감싸주면 모든 페이지에 적용된다.
import "../styles/globals.css";
import Layout from "../components/Layout";
export default function App({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
getServerSideprops, getStaticProps, getStaticPaths
요약
- Next.js의 데이터 패칭 방식은 getStaticProps, getStaticPath 와 getServerSideProps이 있다.
- getStaticProps은 유저의 요청마다 fetch할 필요가 없는 고정된 내용의 페이지를 렌더링할 때,
- getStaticPath는 어떤 페이지를 미리 Static으로 빌드할 지 정하는 api로, getStaticProps + 동적라우팅이 필요할 때 적합하다.
- getServerSideProps는 자주 변경되는 페이지로, 페이지를 렌더링하기 전 반드시 fetch해야 할 데이터가 있을 때 사용한다. 비교적 성능이 떨어지기 때문에 꼭 필요한 곳에만 사용한다.
.next > server > pages > index.html
에 들어가보면 완성된 html 파일을 확인 가능하다. export async function getStaticProps() {
const res = await fetch(
"https://jsonplaceholder.typicode.com/posts?_start=0&_end=10"
);
const posts = await res.json();
return {
props: {
posts,
},
revalidate: 20, //20초 마다 regeneration 발생
};
}
위 fetch는 "빌드 시에 딱 한 번"만 호출되고, 바로 static file로 빌드되어, 빌드 이후 수정이 불가능하다.
data를 빌드시에 미리 호출하여 정적으로 제공하기 때문에 페이지 렌더링 속도가 빠르다.
때문에 유저의 요청마다 다르게 fetch할 필요가 없는 고정된 내용의 페이지를 렌더링할 때 유리하다.
getStaticProps는 빌드 시 데이터를 가져오며 쿼리 매개변수 또는 HTTP 헤더와 같이 요청 시에만 사용할 수 있는 데이터는 사용할 수 없다.
getStaticProps는 revalidate이라는 옵션을 통해 주기적으로 데이터를 패칭하여 SSG와 SSR의 장점이 합쳐진 ISR을 구현할 수 있다.
getServerSideProps는 빌드와 상관없이, page가 요청받을때마다 호출되어 pre-rendering한다.
SSR (Server Side Rendering) 개념으로 pre-render가 꼭 필요한 동적 데이터가 있는 page에 사용한다.
매 요청마다 호출되므로 성능은 getStaticProps에 뒤지지만, 내용을 언제든 동적으로 수정이 가능하다다.
따라서 getServerSideProps는 사용자 대시보드 페이지(내정보 페이지)에 적합하다. 대시보드는 사용자 고유의 개인 페이지이므로 SEO는 관련이 없으며 페이지를 미리 렌더링할 필요가 없으며, 데이터는 자주 업데이트되므로 요청 시간 데이터를 가져와야 한다.
Next.js는 getServerSideProps를 정말 필요할 때만 사용하라고 권고한다. CDN에 캐싱되지 않기 때문에 느리기 때문이다. 데이터를 미리 가져올 필요가 없다면 클라이언트 측에서 데이터를 가져오는 것도 고려해봐야 한다.
동적 라우팅 + getStaticProps
를 원할 때 사용한다.
페이지가 동적 라우팅을 쓰고 있고, getStaticProps를 쓰는 경우, getStaticPaths을 통해 빌드 타임 때 정적으로 렌더링할 경로를 설정해야 한다. 여기서 정의하지 않은 하위 경로는 접근해도 화면이 뜨지 않는다.
라우팅 되는 path의 경우의 수를 모두 알려줘야한다는 뜻이다.
📂 photos
📂 [id]
- index.js
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
const index = ({ photo }) => {
return (
<div>
생략
</div>
);
};
// context는 getStaticPaths에서 넘겨준 값
export const getStaticProps = async (context) => {
const { id } = context.params;
const res = await fetch(`https://jsonplaceholder.typicode.com/photos/${id}`);
const photo = await res.json();
return {
props: {
photo,
},
};
};
// getStaticPaths를 사용해 path의 모든 id를 담은 배열을 리턴한다.
export const getStaticPaths = async () => {
const res = await fetch(
"https://jsonplaceholder.typicode.com/photos?_start=0&_end=10"
);
const photos = await res.json();
const ids = photos.map((photo) => photo.id); // id 10개 뽑기
const paths = ids.map((id) => { // paths 배열로 만들기
return {
params: { id: id.toString() },
};
});
return { // 리턴
paths: paths,
fallback: false,
};
export default index;
getStaticPaths에서 data fetch없이 직접 paths 배열 만들어 리턴해줘도 된다.
(만약 하위 path가 수백 수천개라면.. 이 방법은 불가능)
return {
paths: [
{params : { id : '1' }},
{params : { id : '2' }},
{params : { id : '3' }},
],
fallback: false,
};
만약 동적 라우팅 + getStaticProps
의 조합이 아니라면 getStaticPaths
를 사용하지 않아도 되므로, useRouter를 사용해 url에서 바로 path를 뽑아 사용하면 된다. (기존 React에서 하던 방식)
import { useRouter } from "next/router";
const router = useRouter();
console.log(router);
유튜브 강좌를 참고해 작업했습니다.
: Next JS 실습 강좌! React를 더욱 편리하게 SEO
1. 배포된 주소
2. 소스 코드 보러가기(깃헙)