React 라이브러리
의 프레임워크
이다.
react.js component를 export하고 있는 파일을 pages
폴더 아래에 두기만 하면 next.js가 파일의 이름
을 가져다가 url의 이름
으로 쓸 것이다.
컴포넌트(함수)의 이름은 중요하지 않다. 파일 이름과 달라도 된다.
중요한 것은 아래와 같이 컴포넌트(함수)를 default로 export
해야 한다는 것이다.
index.js
export default function Home(){
// code...
}
사용자에게 보여주고 싶은게 있다면
pages
폴더 안에서 파일을export default function
을 해라. next.js에서는 pages 폴더가 라우터의 기능을 하기 때문이다!
만약, create-react-app 만으로 프로젝트를 수행하게 된다면, React router DOM을 다운받아서 router를 따로 만들고, routes를 설계하고, component를 import하고, router를 render하고 등등.. 하지만, next.js는 이 모든 것이 이미 되어있는 것이다!
위처럼 존재하지 않는 파일의 이름을 url에 /about-us
처럼 입력하면, 404 페이지가 뜨는 것을 알 수 있다. 만약, CRA(create-react-app)
를 사용했다면 이 페이지도 직접 만들어야 했을 것이다.
여기서 하나의
예외사항
이 존재한다.
그것은 pages 폴더 아래 index.js라는 파일은 우리 앱의 홈페이지로 연결시켜준다.
/
경로와 같다. 가장 기본 화면의 역할을 한다. next.js는 기본적으로index.js
를 띄워주기 때문이다!
next.js 앱 내에서 페이지를 navigate
할 때 사용하는 특정 컴포넌트가 존재한다. !
<a href='/about'>About</a>
이 아닌
<Link href='/about'>
<a>About</a>
</Link>
와 같이 사용한다.
이렇게 하면 페이지가 이동할 때, 새로고침이 되지 않고 페이지 이동을 할 수 있다. (react SPA
는 전체 페이지를 다시 렌더링하지 않고 변경되는 부분만
을 갱신하므로 새로고침이 발생하지 않아 효율적이며 네이티브 앱과 유사한 사용자 경험을 제공할 수 있다.)
a tag를 Link tag의 child로 사용했을 때, Error: Invalid <Link> with <a> child. Please remove <a> or use <Link legacyBehavior>.
와 같은 Error
가 발생함을 알 수 있는데, a tag를 지우거나 Link tag에 legacyBehavior
를 추가해주라고 한다.
여기서 주의해야 할 점은 a tag를 제거하면 style을 적용하지 못한다는 점이다. Link tag에는 className도, style도 적용되지 않는다. 이 Link는 단지 href만을 위한 것이라고 볼 수 있다!
아래와 같이 Link tag에 legacyBehavior
를 추가하여 사용한다.
<Link href='/' legacyBehavior>
<a>Home</a>
</Link>
router
를 이용하여 Home 또는 About link를 클릭하였을 때 a tag에 style
을 적용해보자. (조건자 사용)
NavBar.js
import Link from "next/link";
import { useRouter } from "next/router";
export default function NavBar() {
const router = useRouter();
return (
<nav>
<Link href="/" legacyBehavior>
<a style={{ color: router.pathname === "/" ? "red" : "blue" }}>Home</a>
</Link>
<Link href="/about" legacyBehavior>
<a style={{ color: router.pathname === "/about" ? "red" : "blue" }}>
About
</a>
</Link>
</nav>
);
}
next.js에서는 CSS Modules을 통해 tag에 style을 적용하는 것이 아닌 우리에게 익숙한 방식인 파일을 생성해 CSS를 사용해 style
을 적용한다.
이 CSS 모듈을 적용할 때 주의할 점이 있다.
그것은 바로 클래스 이름을 추가할 때 텍스트로서 적지 않는다는 것이다.
아래 코드의 nav tag같이 자바스크립트 오브젝트에서의 프로퍼티 형식으로 적어야 함을 기억하자.
NavBar.js
import Link from "next/link";
import { useRouter } from "next/router";
import styles from "./NavBar.module.css";
export default function NavBar() {
const router = useRouter();
return (
<nav className={styles.nav}>
<Link href="/" legacyBehavior>
<a>Home</a>
</Link>
<Link href="/about" legacyBehavior>
<a>About</a>
</Link>
</nav>
);
}
F12를 통해 nav의 className을 살펴보자.
위와 같이 우리가 설정한 className={styles.nav}
가 무작위 텍스트로 nav의 className으로 추가된 것을 알 수 있다. 이는 클래스 이름의 중복 고민 없이 얼마든지 재사용할 수 있다는 장점이 있다.
CSS Modules
을 사용하여 위에서 처럼 Home 또는 About link를 클릭하였을 때 a tag에 style
을 적용해보자. 아래와 같이 작성해볼 수 있을 것이다.
<nav>
<Link href="/" legacyBehavior>
<a className={router.pathname === '/' ? styles.active : ""}>Home</a>
</Link>
<Link href="/about" legacyBehavior>
<a className={router.pathname === '/about' ? styles.active : ""}>About</a>
</Link>
</nav>
또 다른 클래스를 추가하고 싶다면?
동일한 앨리먼트에 여러개의 클래스를 적용하고 싶다면 두가지 방식이 있다.
active 클래스를 추가해보자.
1. 백틱
을 사용하여 특정 문자열로 만들어 삽입하기
<Link href="/about" legacyBehavior>
<a className={`${styles.link} ${router.pathname === '/about' ? styles.active : ""}`}>About</a>
</Link>
2. 배열
로 삽입하기
<Link href="/about" legacyBehavior>
<a
className={[
styles.link,
router.pathname === "/about" ? styles.active : "",
].join(" ")}
>
About
</a>
</Link>
배열을 문자열로 변환할 때 join
이라는 함수를 사용하는데, 이때 한칸의 공백
을 두고 join
하는 것을 잊지말자!
CSS의 자동완성 기능을 위해 먼저 vscode의 확장에서 아래와 같은 extension을 설치하시길 바랍니다.
style tag는 일반 HTML 태그이고, 아래와 같이 이 태그에 jsx
를 삽입해보자.
NavBar.js
<style jsx>{`
nav {
background-color: tomato;
}
a {
text-decoration: none;
}
`}</style>
클래스 이름을 따로 만들 필요없이 태그 자체로 스타일들을 지정할 수 있겠구나!
이러한 방식은 모듈이 독립적
으로 되어있어 다른 파일(부모 컴포넌트)에서 같은 태그에 다른 style을 적용해도 전혀 적용되지 않는 것을 볼 수 있다.
현재 파일의 코드 내에서와 동일 태그 혹은 동일한 이름을 가진 클래스의 스타일을 다른 파일에서 설정하더라도,
styled jsx
를 사용하기 때문에 해당 컴포넌트에 적용한 스타일은 오직 그 컴포넌트에서만 스타일이 적용되도록해당 컴포넌트 내부로 범위가 한정
되어 적용된다!
무작위 텍스트로 생성되는 클래스 이름을 볼 수 있다.
styled jsx
를 사용하여 앞서 Home 또는 About link를 클릭하였을 때의 a tag에 style
을 적용해보자. 아래와 같다.
<Link href="/" legacyBehavior>
<a className={router.pathname === "/" ? "active" : ""}>Home</a>
</Link>
<Link href="/about" legacyBehavior>
<a className={router.pathname === "/about" ? "active" : ""}>About</a>
</Link>
<style jsx>{`
nav {
background-color: tomato;
}
a {
text-decoration: none;
}
.active {
color: green;
}
`}</style>
이제 여기까지 왔으면, 생기는 의문점이 있을것이다.
모든 모듈이 독립적이면,,
동일한 태그에 전역적으로 스타일을 동일하게 적용하고 싶으면 ㅇ ㅓ떻게해?..
(font-size, font-color, font-family 등...)
이는 헤더 혹은 네비게이션 혹은 풋터 등 공통으로 사용하는 것들이 존재하거나 또한 페이지 전체에 적용하고싶은 공통 css를 적용하기 위해 편리하다! (커스터마이징하는 것이 의무가 아니라는 뜻이다.)
커스터마이징을 하기 위해서는 _app.js
라는 파일을 만들어야 한다. (이 경우에 무조건 이 이름이여야함을 주의하자!) NextJS에서 최초로 실행되는 것은 _app.js
이기 때문이다. NextJS는 _app.js
를 먼저 확인하고 그 다음에 index.js
의 내용물들을 렌더링한다.
Custom App
을 생성하여 Component
, pageProps
라는 props를 전달한다. (이건 필수! next.js 프레임워크가 정한 것이다.)
NextJS는 아래와 같이 렌더링
하기 원하는 첫번째 페이지를 Component
로서 넣어줄 것이다. props로 받은 Component는 요청한 페이지이다.
export default function App({Component, pageProps}){
return <div>
<Component {...pageProps} />
</div>;
}
이때, 컴포넌트 이름은 자유자재로 정하면 된다. 파일 이름 _app.js
에만 주의하자!
NextJS 에서는 css 파일을 import할 수 없다. css를 import하고 싶다면, 반드시 module
이어야 한다.
하지만, Custom App
컴포넌트가 있는 곳에서라면 모든 Global Styles를 import할 수 있다.
두번째 인자인 pageProps와 함께 SSR(Server Side Rendering)에 대하여 이야기 해보자.
next.js
의 가장 좋은 기능 중 하나는 앱에 있는 페이지들이 미리 렌더링 된다는 것이다. 이것들은 정적(static)으로 생성된다.
create-react-app
은 Client-Side Rendering
기법을 사용하는데, 이는 브라우저가 유저가 보는 UI를 만드는 모든 것을 한다는 것을 의미한다. 사용자가 웹사이트를 요청했을 때 빈 html을 가져와 script를 로딩하기 때문에 브라우저는 빈 화면을 띄우고, 코드를 요쳥한 후에 코드가 왔을 때 비로소 사용자에게 UI를 보여준다. 자바스크립트, react 등 모든 것을 fetch 한 후에야 UI가 보인다는 것이다. 이처럼, 유저가 처음 흰 화면만 보게 되는 것은 썩 좋은 방식이 아니다. 이는 첫 로딩시간도 오래 걸리고 SEO(Search Engine Optimization, 검색엔진최적화)에 취약하다는 단점이 있다.
반면, Server-Side Rendering
기법을 사용하는 create-next-app
은 사용자가 기다릴 필요없이 미리 렌더링되어 즉시 UI를 볼 수 있게 된다. next.js는 초기 상태로 pre-rendering을 수행한다. 이를 통해 미리 데이터가 렌더링된 페이지를 가져올 수 있게 해주므로 사용자에게 보다 긍정적인 경험을 제공해주고, 검색 엔진에 잘 노출될 수 있게 해주는 장점이 있다.
본격적으로 server-side로 어떻게 rendering되는지 살펴보자.
getServerSideProps
라는 이름을 가진 컴포넌트를 index.js
에 아래와 같이 생성해준다. 이 컴포넌트는 오직 server side에서만 실행된다. ! 그리고 Home
컴포넌트에서만 나타나게 된다.
export default function Home({results})
export async default getServerSideProps(){
const { results } = await (await fetch(`/api/movies`)).json();
return {
props: {
results,
}
}
}
getServerSideProps
컴포넌트에서 무엇을 return 하던 이것을 props
로써 Home
Page에게 넘겨주게 되는 것이다.
다시 _app.js
을 살펴보며 진행 방식을 설명하자면,
export default function App({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
Home
Page로 이동하려고 할 때, next.js가 Home을 받아서 render 하기위해 Component 자리에 넣을 것이고,getServerSideProps
를 호출한다.{...pageProps}
자리에 넣게 된다.이런 방식으로 rendering을 수행하는 웹 사이트의 경우 API가 fetch 될 때 까지 화면에 아무것도 보이지 않게 되는데, 여기서 우리는 SSR 방식을 사용하여 Loading 화면없이 데이터가 유효할 때까지 기다린 후에 화면이 보여지게 할 것인지 Loading 화면을 띄운 후 데이터를 받게되면 화면이 보여지게 할 것인지 선택하여야 한다.
Layout.js
라는 react component를 생성한다.
너무 큰 _app.js 파일을 원하지 않기 때문이다.
Layout.js
import NavBar from "./NavBar";
export default function Layout({ children }) {
return (
<div>
<NavBar></NavBar>
<div>{children}</div>
</div>
);
}
children
이란 react.js가 제공하는 prop으로, 하나의 컴포넌트를 또 다른 컴포넌트의 자식 컴포넌트로 넣을 때 사용할 수 있다.
_app.js
import Layout from "../components/Layout";
import "../styles/globals.css";
export default function App({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}