728x90
반응형

ISR(Incremental Static Regeneration)

ISR(Incremental Static Regeneration) 을 사용하면 트래픽이 들어올 때 백그라운드에서 다시 렌더링해서

기존 페이지를 업데이트 할 수 있게 해준다. 아래와같이 코드를 작성해보자.

 

about.js

function About({posts}) {
  console.log(posts)
    return (
      <ul>
        {posts.map((post) => (
          <li key={post.id}>{post.name}</li>
        ))}
      </ul>
    )
  }
  
export default About

export async function getStaticProps(context) {
    const res = await fetch(`https://jsonplaceholder.typicode.com/users`)
    const posts = await res.json()

  return {
    props: {
      posts,
    },
    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every second
    revalidate: 1, // In seconds
  }
}

 

실행해보자.

 

 

 

이제 블로그의 포스트리스트는 1초마다 재생성된다. 만약 새로운 블로그 포스트가 추가되면 앱을 재빌드하거나 재배포없이 거의 곧바로 활용가능해진다.

 

이것은 또한 fallback:true 옵션과 완벽히 동작한다.

 

Static content at scale

전통적인 SSR과는 다르게 ISR은 정적특징의 장점을 보장해준다.

  • 지연시간이 급증하지않고, 페이지는 지속적으로 빠르게 제공된다.
  • 페이지가 오프라인이 되지 않으며 만약 재생성이 실패해도 예전 페이지는 변경되지 않고 유지된다.
  • db나 백엔드에 로드 부하가 작다. 페이지들이 동시에 한번 재계산되기 때문이다.

 

파일 읽기 : process.cwd()

파일들을 getStaticProps 에서 직접 파일시스템을 통해 읽혀 질 수 있다. 그러기 위해선 반드시 파일의 full path를 가지고 있어야 한다.

 

Next.js는 코드를 몇개의 디렉토리 속으로 나누어 컴파일한다. 따라서 __dirname을 사용하면 실제 페이지 디렉토리 경로와 다른 경로를 리턴하기 떄문에 사용할 수 없다.

 

대신 우리는 process.cwd()를 사용할 수 있다. 이것은 Next.js 실행이 일어나는 디렉토리 정보를 준다.

 

about.js

import { promises as fs } from 'fs'
import path from 'path'

function About({posts}) {
  console.log(posts)
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <h3>{post.filename}</h3>
          <p>{post.content}</p>
        </li>
      ))}
    </ul>
  )
  }
  
export default About

export async function getStaticProps(context) {
  const postsDirectory = path.join(process.cwd(), 'posts')
  const filenames = await fs.readdir(postsDirectory)

  console.log(postsDirectory)
  console.log(filenames)

  const posts = filenames.map(async (filename) => {
    const filePath = path.join(postsDirectory, filename)
    const fileContents = await fs.readFile(filePath, 'utf8')

    // Generally you would parse/transform the contents
    // For example you can transform markdown to HTML here

    return {
      filename,
      content: fileContents,
    }
  })
  // By returning { props: { posts } }, the Blog component
  // will receive `posts` as a prop at build time
  return {
    props: {
      posts: await Promise.all(posts),
    },
  }
}

 

이렇게 만들고 posts폴더를 만든 후 abc.txt 파일을 생성해보자.

 

 

abc.txt

abc

 

실행 해보면 아래 화면과 로그를 확인할 수 있다.

 

 

posts 폴더 하위에 있는 파일들의 이름과 내용을 찾고 About 컴포넌트로 넘겨주었다.

 

 

Write server-side code directly

getStaticProps는 서버사이드에서만 동작한다. 클라이언트 사이드에서는 동작하지 않는다. 또한 브라우저에서 확인 가능한 js bundle에 조차 포함되지 않는다. 이것이 의미하는 것은 direct database queries를 직접 사용해서 정보를 가져올 수 있다는 것이다. getStaticProps로부터 API route를 fetch하면 안된다. 

 

getStaticProps(정적 생성)는 빌드 시 데이터를 가져오고 그곳에서만 처리됩니다. 즉, 프론트엔드와 백엔드가 혼합되지 않습니다. getStaticProps에 데이터가 필요한 경우 데이터를 가져오기 위해 /api에 대한 추가 네트워크 호출을 발생시키지 않아야 합니다. 대신 해당 논리와 함께 함수를 사용하거나 getStaticProps에서 직접 함수를 사용해야 합니다. 프론트엔드에 데이터가 필요한 경우 rest/graphql/whatever/api에서 데이터를 가져오거나 getServerSideProps를 통해 전달할 수 있습니다.

 

아래 예제를 통해서 js bundle에서 무엇이 제거되는지 확인할 수 있다( 브라우저에서 )

// This app shows what Next.js bundles for the client-side with its new SSG
// support. This editor supports TypeScript syntax.
import Cookies from 'cookies';
import Mysql from 'mysql';
import Link from 'next/link';
import SQL from 'sql-template-strings';
import Layout from '../components/Layout';

const pool = Mysql.createPool(process.env.DATABASE_URL);

export default function ({ projects }) {
  return (
    <Layout>
      <h1>Projects</h1>
      <ul>
        {projects.map((project) => (
          <li key={project.id}>
            <Link href="/projects/[id]" as={`/projects/${project.id}`}>
              <a>{project.name}</a>
            </Link>
          </li>
        ))}
      </ul>
    </Layout>
  );
}

export async function getServerSideProps({ req, res }) {
  const userId = new Cookies(req, res).get('user_id');
  const projects = await new Promise((resolve, reject) =>
    pool.query(
      SQL`SELECT id, name FROM projects WHERE user_id = ${userId};`,
      (err, results) => (err ? reject(err) : resolve(results))
    )
  );
  return { props: { projects } };
}
// This is the code that is bundled for the client-side:

import Link from 'next/link';
import Layout from '../components/Layout';
export var __N_SSP = true;
export default function ({ projects }) {
  return (
    <Layout>
      <h1>Projects</h1>
      <ul>
        {projects.map((project) => (
          <li key={project.id}>
            <Link href="/projects/[id]" as={`/projects/${project.id}`}>
              <a>{project.name}</a>
            </Link>
          </li>
        ))}
      </ul>
    </Layout>
  );
}

 

 

Statically Generates both HTML and JSON

getStaticProps를 가지고 있는 페이지가 빌드 시 pre-render 될 때, Next.js는 HTML파일 뿐 아니라 getStaticProps의 실행 결과인 JSON을 생성한다.

 

이것은 클라이언트에서 getStaticProps를 호출하는것이 아니라 단지 export된 JSON을 사용한다는 것을 의미한다.

 

 

Only allowed in a page

getStaticProps는 page에서만 exported 된다. 다른 곳에서는 사용할 수 없다. 이유는 리액트는 페이지가 render되기 전에 data 모두를 가지고 있어야 하기 때문이다. 

 

또한 반드시 export async function getStaticProps() {}로 동작해야한다.

TypeScript쓸 때는 export const getStaticProps: GetStaticProps = async (context) => {}

 

 

Run on every request in development

개발환경에서 getStaticProps는 매 요청시에 호출된다.

 

 

Preview Mode

요청시에 페이지가 render 되길 원하는 경우가 생긴다. 배포되기전에 프리뷰 초안을 보길 원할 수 있다.

자세한 내용은 Preview Mode 문서를 참조하자.

 

 

 

728x90
반응형
728x90
반응형

페이지 내부에서 SSR을 할 때 getStaticProps 를 사용하여 빌드 시 이 페이지를 미리 렌더링한다.

 

pages폴더 하위에 about.tsx 파일을 만들고 다음과 같이 작성해보자.

 

/pages/about.js

function About({data}) {
    console.log(data)
    return <div>About</div>
  }
  
  export default About

  export async function getStaticProps(context) {
    return {
      props: {
          data : "Teepo"
      }, // will be passed to the page component as props
    }
  }

 

아래 getStaticPropspropsdata를 담아서 About 컴포넌트로 보냈다.

 

npm run dev 명령어를 입력하고 확인해보자.

$ npm run dev

 

 

getStaticProps로 전달한 data값이 잘 넘어갔다.

 

나중에 fetch나 axios를 이용할 경우 다음과 같이 구현할 수 있다.

 

about.js

function About({data}) {
    console.log(data)
    return <div>About</div>
  }
  
export default About

export async function getStaticProps(context) {
    const res = await fetch(`https://jsonplaceholder.typicode.c/users`)
    const data = await res.json()

    return {
      props: { data }, // will be passed to the page component as props
    }
}

 

실행화면은 다음과 같다.

 

 

또한 redirect를 반환하여 리디렉션을 할 수 있다.

 

export async function getStaticProps(context) {
    const res = await fetch(`https://jsonplaceholder.typicode.com/users`)
    const data = await res.json()

    if (!data) {
      return {
        redirect: {
          destination: '/',
          permanent: false,
        },
      }
    }
  
    return {
      props: { data }, // will be passed to the page component as props
    }
}

 

다만, 빌드 시 리디렉션은 현재 허용되지 않으며 빌드 시 알려진 경우에 추가해야될 것들이 있다.

자세한 내용은 아래 주소로 확인해보자

 

https://nextjs.org/docs/api-reference/next.config.js/redirects

 

next.config.js: Redirects | Next.js

Add redirects to your Next.js app.

nextjs.org

 

 

언제 getStaticProps를 사용할까?

  • render되는 페이지가 필요한 데이터가 사용자 요청 전 빌드시에 활용가능한 data 일때
  • data가 headless CMS에서 오는 data일때
  • data가 사용자개인화가 필요하지 않은 공공성의 캐시된 data일때
  • 해당 페이지가 SEO를 위해 반드시 pre-render 된 페이지 여야하고 매우 빨리 로드되어야 할 때, -- getStaticProps는 HTML과 JSON파일을 생성하고 둘 다 CDN에 의해 캐시되서 성능적으로 매우 좋다.

 

TypeScript - getStaticProps

타입스크립트를 사용한 about 페이지는 다음과 같다.

 

about.tsx

import { GetStaticProps } from 'next'

interface data  {
    name : string
    age : number
}

function About(data : data) {
    console.log(data)
    return <div>About</div>
  }
  
export default About

export const getStaticProps: GetStaticProps = async (context) => {

    const res = await fetch(`https://jsonplaceholder.typicode.com/users`)
    const data = await res.json()

    if (!data) {
      return {
        redirect: {
          destination: '/',
          permanent: false,
        },
      }
    }
  
    return {
      props: { 
          data: {
              name : "Teepo",
              age : 28
          }
        }, // will be passed to the page component as props
    }
}

GetStaticProps를 import 하고 data를 Object로 만들어보았다.

 

728x90
반응형
728x90
반응형

Next.js의 특징 중 하나는 파일 이름을 기반으로 라우팅이된다.

 

예를들어 다음과같이 코드를 작성하면

 

pages/about.js

function About() {
  return <div>About</div>
}

export default About

이 파일은 /about 페이지가 된다.

 

Pages with Dynamic Routes

Next.js는 동적라우트 할 수 있는 페이지를 제공한다. pages/posts/[id].js 파일을 생성한다면

posts/1, posts/2에 접근할 수 있다.

 

Pre-Rendering

Pre-Rendering은 Next.js에서 중요한 컨셉 중 하나다. 기본적으로 모든 페이지를 pre-rendering한다. 

이는 Next.js가 client-side JavaScript로 작업을 수행하기 전 미리 각 페이지에서 HTML을 만들어 두는 것을 의미한다.

js 기능을 끄더라도 페이지를 볼 수 있다.

 

Pre-Rendering에는 2가지 과정이 있다.

  1. initial load
  2. hydration

 

initial load html

js 동작만 없는 html을 먼저 화면에 보여주는데, 아직 js파일이 로드되기 전 이므로 <Link> 같은 컴포넌트는

동작하지 않는다.

 

hydration

initial load에서 html을 로드한 뒤 js 파일을 서버로부터 받아 html을 연결시키는 과정이다.

여기서 js와 html이 연결된다.

 

 

Pre-Rendering이 없다면?

 

 

JS 전체가 로드되어야 하기 떄문에 최초 Load에서 사용자에게 아무것도 보여지지 않게 된다.

 

Static Generation with data

pre-rendering 시에 외부 data가 필요할 경우 Next.js가 제공하는 특별한 함수를 사용할 수 있는데,

아래 함수 중 한개 혹은 두개 모두 사용이 필요할 수 있다.

 

  1. 만약 페이지 content가 외부 data에 종속된 경우 : getStaticProps를 사용한다.
  2. 페이지 paths가 외부 data에 종속된 경우 : getStaticPaths를 사용한다.(보통getStaticProps도 같이사용한다.)

보통은 Static Generation을 권장한다. 매 요청시마다 페이지를 render하는 SSR보다 훨씬 빠르기 때문이다.

Static Generation은 아래 경우들에 사용할 수 있다.

  • Marketing pages
  • Blog posts
  • E-commece product listings
  • help and documentation

하지만 매 요청에 따라 다른 데이터를 보여주기 위해서는 SSR을 써야한다.

 

 

728x90
반응형
728x90
반응형

참조 

https://nextjs.org/docs/getting-started

 

Getting Started | Next.js

Get started with Next.js in the official documentation, and learn more about all our features!

nextjs.org

 

 

시스템 요구 사항

 - Node.js 12.0 이상

 - MacOS, Windows 및 Linux 지원

 

 

먼저 다음 명령어로 Next.js 앱을 설치한다.

$ npx create-next-app@latest

 

만약 TypeScript로 프로젝트를 실행하려면 다음 명령어를 실행한다.

$ npx create-next-app@latest --typescript

 

 

  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  • dev - 개발 모드에서 Next.js를 시작하는 실행
  • build - 'next build' 프로덕션을 사용
  • start - 'next start' 프로덕션 사용
  • lint - 'next lint' Next.js의 내장 ESLint 구성을 설정
728x90
반응형
728x90
반응형

npm install next@latest react@latest react-dom@latest 실행하면 됩니다.

(저는 next@lastest만 실행했었는데 이런 오류가 떴었습니다.)

728x90
반응형

'Front-End > Next.js' 카테고리의 다른 글

Next.js | 기본기능 | Data fetching(1)  (0) 2021.10.08
Next.js | 기본기능 | Pages  (0) 2021.10.08
Next.js | 기본기능 | 시작하기  (0) 2021.10.08
Next.js 렌더링  (0) 2021.08.31
Next.js | socket.io Tips  (0) 2021.08.31
728x90
반응형

정적 서버렌더링(getStaticProps)는 한번 불러오면 수정이 불가능하다. 따라서 왠만해서는

바꾸지 않는 내용들이 필요할 때 사용

 

동적 서버렌더링(getServerSidePorps)은 동적으로 데이터가 자주 바뀌는 페이지에 유용.

 

Next js 안에서 서버사이드 렌더링으로 db작업을 할 수는 있다.

서버 과부하가 왔을 때 express로 백엔드 서버를 만들고 하면

프론트 서버와 백엔드 서버중 과부하 되는 서버를 늘리기만 하면 되지만

Next js 안에서만 돌게하면 프론트 서버가 너무 무거워진다.

 

프론트엔드 서버 : 쿠키작업, 프론트 안에서 돌아가는 작업들( 로그인, 장바구니 등)

           state가 프론트의 요청으로 바뀔 때

백엔드 서버 : DB작업, node_modules 사용할 작업

           → state가 백엔드의 요청으로 바뀔 때

           사실상 서버사이드 렌더링으로 프론트엔드에서도 node_module 사용 가능하다

           → DB작업은 차후에 GraphQL로 대체할 예정

 

Pages 에서 server, client 영역으로 나누고 각각에 맞는 요청을 정리할 것.

(node_modules를 어디서 쓸 지도)

 

728x90
반응형
728x90
반응형

 

const express = require('express');

const next = require('next');

const morgan = require('morgan');

const cookieParser = require('cookie-parser');

const expressSession = require('express-session');

const dotenv = require('dotenv');

 

const dev = process.env.NODE_ENV !== 'production';

const prod = process.env.NODE_ENV === 'production';

 

dotenv.config();

 

const app = next({ dev }); // next 모듈을 사용

const handle = app.getRequestHandler();

 

//socket

const socketapp = require('express')();

const socketserver = require('http').createServer(socketapp)

const cors = require('cors');

socketapp.use(cors());

 

const ports = {

  next: process.env.NEXT_PUBLIC_React_Port,

  socket: process.env.NEXT_PUBLIC_Socket_Port

};

 

app.prepare().then(() => {

 

  //nextserver

  const server = express(); // back 서버에서의 const app = express()

  server.use(cors());

  server.use(morgan('dev'));

  server.use(express.json());

  server.use(express.urlencoded({ extended: true }));

  server.use(cookieParser(process.env.COOKIE_SECRET));

  server.use(

    expressSession({

      resave: false,

      saveUninitialized: false,

      secret: process.env.COOKIE_SECRET, // backend 서버와 같은 키를 써야한다.

      cookie: {

        httpOnly: true,

        secure: false,

      },

    }),

  );

  

  server.get('/hashtag/:tag', (req, res) => {

    return app.render(req, res, '/hashtag', { tag: req.params.tag });

  });

 

  server.get('/user/:id', (req, res) => {

    return app.render(req, res, '/user', { id: req.params.id });

  });

 

  server.get('*', (req, res) => { // 모든 get 요청 처리

    return handle(req, res); // next의 get 요청 처리기

  });

  server.listen(ports.next, () => {

    console.log('next+expresss running on port 3000');

  });

 

  //socketserver

  const io = require('socket.io')(socketserver,{

      cors : {

          origin: "http://"+process.env.NEXT_PUBLIC_IP+":"+process.env.NEXT_PUBLIC_React_Port, //해보니까 localhost는 안됨

          methods: ["GET", "POST"],

          allowedHeaders: ["*"],

          credentials: true,

      }

  });

 

  io.on('connection', socket=>{

      // socket으로 메세지가 들어올 때 

      socket.on('message',({chatList}) => {

          console.log("@@@")

          // 메세지가 들어올 때 보내줌

          io.emit('message',({chatList}))

      })

  })

 

  socketserver.listen(ports.socket, function(){

    console.log('listening on socket port 3001');

    })

});

 

1.     넥스트 서버를 동적분할하고 소켓서버를 열 수 있다.

2.     소켓서버에 cors설정을 해주고(json으로 된것들), 넥스트서버에도 cors 설정 해줘야함

728x90
반응형

+ Recent posts