// 어플리케이션에 사용되는 정적 파일들
public
├── asset
├── image
└── video
// 어플리케이션의 기능에 사용되는 소스들
component // 컴포넌트들 모음
├── common // 공통적으로 사용되는 컴포넌트들(ex button, modal)
└── layout // 각 페이지들의 전체 레이아웃이자 레이아웃 내부 컴포넌트들의 모임
├── layout.tsx
├── header.tsx
├── nav.tsx
└── footer.tsx
pages // 페이지를 담당하는 컴포넌트(폴더구조로 url 결정)
│ // (❗ Nextjs에서는 Routing 시스템이 파일/폴더 구조로 되어있다.)
├── index.tsx
├── _app.tsx // 각 페이지별로 공통적으로 쓰는 부분에 대한 리펙토링을 해주는 곳
│ // (index.js파일과 같은 역할, 모든 페이지에서 쓰는 스타일, 레이아웃 을 넣어 주기)
├── _document.tsx // meta태그를 정의 및 전체 페이지의 구조를 만들어준다
│ // (index.html파일과 같은 역할, html body와 같은 기본 태그들의 속성지정하여 어플리케이션의 구조를 만들어 주는 파일)
├── api // axios를 이용하여 서버와의 통신 로직을 담당
└── product
└── [id].tsx
styles // 스타일 관련 파일 모음
├── globals.css
└── Home.modules.css
core // redux 사용의 경우, core에서 관리하기
├── config // 어플리케이션에서 사용되는 설정들 모음
├── provider
│ ├── helper // store를 이용하는 공통 로직들 정리
│ └── provider // store의 값들을 컨테이너에게 전달해 줌
└── redux // Ducks 패턴으로 redux를 정의
❗
_app.js
와_document.js
- 최초로 실행되는 파일들이다(
_app.js
실행(최초 실행) 이후_document.js
실행)- pages 폴더 내에 위치
- 두 파일 모두 server only file (client 에서 사용하는 로직을 적용할 수 없다 -> eventListner 등)
👉_app.js
전체 컴포넌트의 레이아웃으로 이해할 수 있다. 공통 레이아웃 이므로 최초에 실행되어 내부에 들어갈 컴포넌트들을 실행한다.
내부에 Content 들이 있다면 전부 실행하고 Html의 Body로 구성한다.function MyApp({ Component, pageProps }) { return ( <Layout> <Component {...pageProps} /> </Layout> ); } export default MyApp; // props로 받은 Component는 요청한 페이지이다. // GET / 요청을 보냈다면, Component 에는 /pages/index.js 파일이 props로 내려오게 된다. // pageProps는 페이지 getInitialProps를 통해 내려 받은 props들을 의미한다.
👉
_document.js
_app.js
다음_documents.js
가 실행된다. static html를 구성하기 위한 _app.js에서 구성한 Html body가 어떤 형태로 들어갈지 구성하는 곳import Document, { Html, Head, Main, NextScript } from 'next/document' class MyDocument extends Document { render() { return ( <Html> <Head /> <body> <Main /> <NextScript /> </body> </Html> ) } } export default MyDocument // _app.js가 실행되면서 갖추어진 content들은 Main Component 아래에 생성된다. // ❗ _documents.js에 어플리케이션 로직을 넣지 말자. // 브라우저는 Main을 제외한 다른 component들을 initialize하지 않는다. // 공통된 어플리케이션 로직이 필요하다면, _app.js를 활용하자.
❗
getInitialProps
웹 페이지는 각 페이지마다 사전에 불러와야할 데이터들이 있다 (Data Fectching).
이는 CSR(Client Side Rendering)에서는 react 로직에 따라 componentDidMount 또는 useEffect로 컴포넌트가 마운트 되고 나서 하는 경우가 많다.
이 과정을 서버에서 미리 처리하도록 도와주는 것이 바로getInitialProps
이다.Next 9.3 버전에서는
getInitialProps
를 대신에 getStaticProps, getStaticPaths, getServerSideProps 를 사용하게 된다고 한다. 각각의 용법은 다르지만, 서버에서 페이지의 연산을 미리 한다는 점은 동일하다.Data Fetching을 서버에서 하게 되면,
첫째, 속도가 빨라진다. 브라우저에서의 연산을 서버와 함께 하면서 미리 데이터를 받아오고 브라우저는 렌더링만 할 수 있기 때문이다.
둘째, 코드 상의 처리가 깔끔해진다. 데이터가 꼭 필요한 페이지의 경우 브라우저가 데이터를 가져올 때까지 화면 렌더링을 잠시 null 처리하는 경우가 있다. 이 과정이 없어지고, Initial한 데이터가 들어오는 과정을 전제로 코드를 작성할 수 있다.
getInitialProps
메서드를 사용할 때, 목적에 따라서 사용법이 다르다. 해당 페이지에만 미리 데이터를 불러오는 로직을 넣을 것인지, 혹은 전체 페이지에 대해 동일한 Data Fetching을 할 것인지를 정해야 한다.
👉 공통된 Data Fetching이 필요하다면_app.js
에 getInitialProps를 붙이면 된다.각 페이지마다 getInitialProps를 붙이는 방법은 아래와 같다.
import axios from 'axios'; const Page = ({ stars }) => { return <div> stars: {stars} </div>; }; Page.getInitialProps = async ctx => { // 해당 페이지에 getInitialProps를 사용해서 data를 받아온다 const { data } = await axios.get('...url'); return { stars: data }; } export default Page;
❗❗❗ 한 페이지를 로드할 때, 하나의 getInitialProps 로직만 실행된다.
예를 들어 _app.js에 getInitialProps를 달아서 사용한다면 그 하부 페이지의 getInitialProps는 실행되지 않는다.
👉getInitialProps
함수의 실행 시점은, 처음 페이지가 로드 되었을 때 서버에서 한 번 실행해주고, 그 다음 Link Component 혹은 라우팅 api 를 사용해 다른 페이지로 이동했을 때, getInitialProps 메서드를 실행해준다.
💡 ServerSide 동작 원리
(1) Next Server가 GET 요청을 받는다.
(2) 요청에 맞는 Page를 찾는다. (기본은 index.js)
(3)_app.js
의 getInitialProps가 있다면 실행한다.
(4)Page Component
의 getInitialProps가 있다면 실행한다. pageProps들을 받아온다.
(5)_document.js
의 getInitialProps가 있다면 실행한다. pageProps들을 받아온다.
(6) 모든 props들을 구성하고,_app.js
>page Component
순서로 rendering.
모든 Content를 구성하고_document.js
를 실행하여 html 형태로 출력한다.
pages 안의 페이지 파일은 코드 최상단에서 import React from 'react';
를 할 필요 없다.
(Next.js 안에서는 Component 단위의 파일을 포함하여, pages 단위의 파일에서도 React를 선언하지 않고 바로 사용한다)
쿼리 파라미터는 /search?keyword=something
의 형태이다
const Index = ({url}) => { return ( 당신이 검색한 키워드는 "{url.query.keyword}" 입니다. ); }; export default Index; // url 주소창에 " localhost:3000/index?keyword=당근 " 이라고 치면 페이지에 아래와 같이 나온다 // "당신이 검색한 키워드는 "당근" 입니다. // 개발자도구를 통해 url props를 열여보면, 다양한 메서드를 확인할 수 있다
페이지 관련 컴포넌트들은 반드시 pages/
디렉토리에 넣어야 한다
/pages
내 구성하는 파일명은 pathname파라미터(/post/:id
)에서 id 부분을 의미한다. (따라서 파일명과 pathname파라미터를 동일하게 설정해 주어야 한다)
즉, pages 폴더에 있는 파일은 해당 이름으로 라우팅 된다.
(pages/post.tsx -> localhost:3000/post)
페이지 라우팅 -> <Link href="/경로">
태그 사용하기
<Link href="/about"> // pages 내 about 파일로 라우팅 됨(파일명 = 경로) <a> 이동하기 </a> </Link>
(만약
<a>
태그를 사용하지 않는다면 해당 컴포넌트가 onClick props를 전달받아서 실행할 수 있게 해야한다)
// (1) Header.js 페이지 생성 import Link from 'next/Link'; const linkStyle = { marginRight: '1rem' } const Header = () => { return ( <div> <Link href="/"><a style={linkStyle}> 홈 </a></Link> <Link href="/about"><a style={linkStyle}> 소개 </a></Link> </div> ) } // (2) Layout.js 페이지 생성 // Layout 내에서, 공용으로 쓸 Header.js 파일을 불러와서 사용한다. import Header from './Header'; const Layout = ({children}) => ( <div> <Header /> {children} </div> ); export default Layout; // (3) pages/_app.js 페이지 import Layout from '../components/Layout'; // layout 컴포넌트 불러온다 const Index = () => { return ( <Layout> 이 부분은 Layout의 children에 해당하는 영역에 들어갈 것입니다. </Layout> ) } export default Index;
_app.tsx
(= index.js)// _app.tsx import "./globals.css"; function MyApp({ Component, pageProps }) { return <Component ponent {...pageProps} />; } export default MyApp;
1) _app
은 클라이언트에서 띄우길 바라는 전체 컴포넌트이다 → 공통 레이아웃이므로 최초 실행되며 내부에 컴포넌트들을 실행함.
(뒤이어 _document.tsx
가 실행됨)
2) 내부에 컴포넌트가 있다면 전부 실행하고 html의 body로 구성됨
3) Component, pageProps를 받음
(여기서 props로 받은 Component는 요청한 페이지이다. GET/ 요청을 보냈다면, Component 에는 /pages/index.js 파일이 props로 내려오게 된다.
pageProps는 페이지 getInitialProps를 통해 내려 받은 props들을 말한다)
4) _app.tsx
에서 consle.log 실행시 client, server둘다 콘솔 찍힌다
_document.tsx
(= index.html)_document
파일에 정의 된 내용들은 SSR일때만 호출된다. CSR에서는 호출 되지 않는다._document.js
파일을 생성 뒤 아래와 같이 작성할 수 있다.// pages/_document.tsx import Document, { Head, Main, NextScript } from 'next/document'; import flush from 'styled-jsx/server' export default class MyDocument extends Document { static getInitialProps ({ renderPage }) { const {html, head} = renderPage(); const styles = flush(); return { html, head, styles }; } render () { return ( <html> <Head> // 모든 페이지에 아래 메타테크가 head에 들어감 // 루트(전역)파일이기에 가능한 적은 코드만 넣어야함. // script tags or link tags <meta property="custom" content="123123" /> <title>Next.js 연습</title> </Head> <body> <Main /> <NextScript /> // _app.js가 실행되면 해당 content들이 생성되는 영역 </body> </html> ); } } // 이곳에서 console은 서버에서만 보이고 클라이언트에서는 안보인다 // 100% static한 상황만 부여한다 (페이지 구성요소만 반영되고 js는 반영 안함) // 이렇게 작성한 경우, 이 값을 기본적으로 설정하고 Head 컴포넌트가 사용된 페이지의 경우엔 이 기본값 위에 덮어씌운다.
getInitialProps
메서드 -> 외부 데이터 가져오기// pages/SSRTest 생성 import Layout from '../components/Layout'; class SSRTest extends React.Component { static async getInitialProps ({req}){ return req ? {from: 'server'} : {from:'client'} } render() { return ( <Layout> 이것은 {this.props.from} 에서 실행이 되었다 </Layout> ) } } // getInitialProps 메소드에서 실행되어 반환되는 값이, 해당 컴포넌트의 props 값으로 전달된다. // getInitialProps 메소드의 파라미터로는, 무조건 서버 측의 request 값이 들어가게 되므로, 클라이언트의 req 값은 undefined가 된다. // 주소로 직접 접속하면(http://localhost:3000/ssr-test 직접입력), // 'server에서 실행되었다'는 문구가 뜨고, ✨ // 페이지 라우팅 링크로 들어가게 되면(<Link href="/ssr-test"> 클릭), // 'client에서 실행되었다'는 문구가 뜨게 된다. ✨
getInitialProps
를 이용하여, API 요청으로 실제 데이터로 fetching을 할 경우 (axios 이용)
// pages/SSRTest 수정 import Layout from '../components/Layout'; import Axios from 'axios'; class SSRTest extends React.Component { static async getInitialProps ({req}){ const response = await Axios.get('https://jsonplaceholder.typicode.com/users'); return { users: response.data } } render() { const {users} = this.props; const userList = users.map( user => <li key={user.id}>{user.username}</li> ) return ( <Layout> <ul> {userList} </ul> </Layout> ) } } export default SSRTest;
[ ]
문법으로 동적 페이지를 생성하는 동적 URL을 만들 수 있다.[id]
는 동적인 어떤 것이던 router의 쿼리 파라미터로서 id파라미터에 담길 수 있다.// pages/blog/[id].tsx import { useRouter } from 'next/router'; // Next.js에서 제공하는 라이브러리로 next/router에서 import받을 수 있다 export default () => { const router = useRouter() // router 객체 초기화 return ( <> <h1> blog post!! </h1> <p> post id: {router.query.id} </p> </> ) } // http://localhost:3000/blog/test 경로를 쳐서 이동하면 <p> 태그 내 내용이 아래처럼 보인다 // post id: test
<Link prefetch href="/ssr-test"><a> SSR 테스트 </a></Link> // 해당 링크를 누를 때 prefetching이 된다
style jsx
를 사용하면 컴포넌트 내부에 해당 컴포넌트만 스코프를 가지는 css를 만들 수 있다.// styled-jsx function Heading(props) { const variable = "blue"; return ( <div className="title"> <h1>{props.heading}</h1> <style jsx> {` h1 { color: ${variable}; } `} </style> </div> ); } export default function Home() { return ( <div> // red <Heading heading="heading" /> // block <h1>ttt</h1> </div> ); }
❗ 전체(글로벌)에 해당하는 스타일링 : style jsx global
은 _app.js
에만 정의 가능하다
작가님 언제 다시 연재 시작하시나요??