Next.js 페이지 구조/특징

kirin.log·2021년 11월 19일
19

⛵ next.js 파일(페이지) 구조

// 어플리케이션에 사용되는 정적 파일들
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 형태로 출력한다.


⛵ next.js 특징

  • 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를 전달받아서 실행할 수 있게 해야한다)

  • 공용 컴포넌트 사용 (components 폴더 내 생성)
    • components/Header.js 파일 생성 (모든 페이지에 공통적으로 쓰일 수 있다)
    • components/Layout.js 파일 생성 (공용 컴포넌트를 담은 틀로 쓸 수 있다)
    • pages/_app.js 파일에 담는다
// (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)
    : meta 태그를 정의하거나, 전체 페이지(root)에 관여하는 컴포넌트이다.
    _document 파일에 정의 된 내용들은 SSR일때만 호출된다. CSR에서는 호출 되지 않는다.
    만일 여러페이지에서 공통적으로 사용하는 헤더를 설정할 경우에는 pages 디렉토리에 _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 메서드 -> 외부 데이터 가져오기
    데이터를 가져올 때, 어떻게 동작하느냐에 따라 서버 사이드 렌더가 될 수도 있고, 클라이언트 사이드 렌더가 될 수도 있다.
    (동시에 사용할 수 있는 방법 - 실행되는 환경이 SSR일때는 Next.js 서버이고 CSR일때는 브라우저이다.)
// 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;
  • 동적 콘텐츠 로드 -> dynamic URL 에 기반하여 제공
    [ ] 문법으로 동적 페이지를 생성하는 동적 URL을 만들 수 있다.
    pages/blog라는 폴더를 예시로 들어보자. blog 폴더 내에는 동적 url로 구성되어야 한다.
    이 방법으로는, pages/blog/[id].tsx 파일을 추가할 수 있다.
    파일 명에서 대괄호 안의 [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
  • prefetch 기능 -> Link 태그에서 prefetch라는 키워드를 입력한다
    ❗ prefetch는 라우팅이 일어나기 전, 데이터를 전부 불러온 후에 라우팅을 일으키는 것을 말한다.
<Link prefetch href="/ssr-test"><a> SSR 테스트 </a></Link>
// 해당 링크를 누를 때 prefetching이 된다
  • 스타일 정의 (특정 컴포넌트 내부에만 해당 vs 전체(글로벌)에 해당)
    • style jsx를 사용하면 컴포넌트 내부에 해당 컴포넌트만 스코프를 가지는 css를 만들 수 있다.
      (= single file components)
  // 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에만 정의 가능하다


profile
boma91@gmail.com

1개의 댓글

comment-user-thumbnail
2022년 5월 30일

작가님 언제 다시 연재 시작하시나요??

답글 달기