
코딩앙마
강의 목록 : https://www.youtube.com/playlist?list=PLZKTXPmaJk8Lx3TqPlcEAzTL8zcpBz7NP
vscode 확장 패키지 추가
1. - Auto Import - ES6, TS, JSX, TSX
2. - Reactjs code snippets
3. - ESLint
4. - Prettier - Code formatter
Visual Studio Code 폴더/파일 아이콘 변경하기
리액트 프로젝트 생성
npx create-react-app 경로
예) npx create-react-app E:\react-app2
넥스트 프로젝트 생성
$ npx create-next-app nextjs-tutorial
nstall next, react and react-dom in your project: npm install next react react-dom # or yarn add next react react-dom # or pnpm add next react react-dom
공식문서 : https://nextjs.org/docs
개발 서버 실행
$npm run dev
 
빌드
$npm run build
빌드처리된 앱 실행
$npm run start
 
1.소개, 페이지 레이아웃 (Intro, Page layout)
2.Dynamic Routes, next/link
3.서버사이드 렌더링 (Server-side Rendering/SSR/Dynamic Rendering)
4.에러 페이지, 환경 변수 (Custom Error Page, Environment Variables)
5.정적 생성(Static Generation) - getStaticProps, getStaticPaths
6.isFallback, getStaticPaths
7.API Routes, 로그인 구현
1.소개, 페이지 레이아웃 (Intro, Page layout)
*create-next-app  으로 설치하면
1.컴파일과 번들링이 자동으로 된다.(webpack 과 babel)
2.자동 리프레쉬 기능으로 수정하면 화면에 바로 반영된다.
3.서버사이드 렌더링이 지원된다.
4.스태틱 파일을 지원합니다.
 
Semantic UI React -> The official Semantic-UI-React integration.
설치:
$ yarn add semantic-ui-react semantic-ui-css ## Or NPM $ npm install semantic-ui-react semantic-ui-css

_app.js
import '../styles/globals.css'
import 'semantic-ui-css/semantic.min.css'
import Footer from '../src/component/Footer'
import Top from '../src/component/Top'
function MyApp({ Component, pageProps }) {
  return (
    <div style={{ width: 1000, margin: " 0 auto" }}>
      <Top />
      <Component {...pageProps} />
      <Footer />
    </div>
  )
}
export default MyApp
/***
 * _app.js
 * 페이지 전환시 레이아웃을 유지할 수 있습니다.
 * 페이지 전환시 상태값을 유지할 수 있습니다.
 * componentDidCatch를 이용해서 커스텀 에러 핸들링을 할 수 있습니다.
 * 추가적인 데이터를 페이지로 주입시켜주는게 가능합니다.
 * 글로벌 CSS 를 이곳에 선언합니다.
 * 
 */
Custom Document : https://nextjs.org/docs/advanced-features/custom-document
_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
    render() {
        return (
            <Html lang='ko'>
                <Head />
                <body>
                    <Main />
                    <NextScript />
                </body>
            </Html>
        )
    }
}
export default MyDocument;
//_document.js 는 서버에서만 랜더링 되지만
//이벤트 핸들러는 작동하지 않는다.
//_document 사용하는 Head 와 _app에서 사용하는 head 는 다르다.
index.js
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
export default function Home() {
  return (
    <div>
      <Head>
        <title>Home | 코딩앙마</title>
      </Head>
      create-next-app  으로 설치하면
      <br />
      1.컴파일과 번들링이 자동으로 된다.(webpack 과 babel)
      <br />
      2.자동 리프레쉬 기능으로 수정하면 화면에 바로 반영된다.
      <br />
      3.서버사이드 렌더링이 지원된다.
      <br />
      4.스태틱 파일을 지원합니다.
    </div>
  )
}
컴포넌트
Top.js
/* eslint-disable @next/next/no-img-element */
import React from 'react';
import { Header } from 'semantic-ui-react';
import Gnb from './Gnb';
const Top = () => {
    return (
        <div>
            <div style={{ display: "flex", paddingTop: 20 }}>
                <div style={{ flex: "100px 0 0" }}>                    
                    <img src="/images/angma.png" alt='logo' style={{ display: "block", width: 80 }} />
                </div>
                <Header as="h1">코딩앙마</Header>
            </div>
            <Gnb />
        </div >
    );
};
export default Top;
Gnb.js
import React, { Component } from 'react'
import { Menu } from 'semantic-ui-react'
const Gnb = () => {
    const activeItem = "home";
    return (
        <Menu inverted>
            <Menu.Item
                name='home'
                active={activeItem === 'home'}
            //onClick={this.handleItemClick}
            />
            <Menu.Item
                name='messages'
                active={activeItem === 'messages'}
            // onClick={this.handleItemClick}
            />
            <Menu.Item
                name='friends'
                active={activeItem === 'friends'}
            //onClick={this.handleItemClick}
            />
        </Menu>
    );
};
export default Gnb;
Footer.js
import React from 'react';
const Footer = () => {
    return (
        <div>Copyright @ 코딩앙마. All right reserved.</div>
    );
};
export default Footer;
2.Dynamic Routes, next/link
axios 설치
npm i axios yarn add axios
const API_URL = "http://makeup-api.herokuapp.com/api/v1/products.json?brand=maybelline";
herokuapp.com 데이터가 원할하지 않은 관계로 thedogapi.com api 사용 (무료) - 회원 가입후 apikey 발급후 적용 할것.
https://docs.thedogapi.com/api-reference/breeds/breeds-list
const API_URL = "https://api.thedogapi.com/v1/breeds";
const API_KEY = ApiKey;



index.js
import axios from 'axios';
import Head from 'next/head'
import Image from 'next/image'
import { useEffect, useState } from 'react';
import styles from '../styles/Home.module.css'
import ItemList from './../src/component/ItemList';
import { Header, Divider } from 'semantic-ui-react';
import ApiKey from '../ApiKey.js';
export default function Home() {
  const [list, setList] = useState([]);
  //const API_URL = "http://makeup-api.herokuapp.com/api/v1/products.json?brand=maybelline";
  //thedogapi.com api 사용
  //https://docs.thedogapi.com/api-reference/breeds/breeds-list
  const API_URL = "https://api.thedogapi.com/v1/breeds";
  const API_KEY = ApiKey;
  function getData() {
    axios.get(API_URL, {
      headers: {
        'x-api-key': API_KEY
      }
    })
      .then(res => {
        // console.log("res.data :", res.data);
        setList(res.data);
      });
  }
  useEffect(() => {
    getData();
  }, []);
  return (
    <div>
      <Head>
        <title>Home | 코딩앙마</title>
      </Head>
      <Header as="h3" style={{ paddingTop: 40, marginBottom: 40 }}>VIP 분양</Header>
      <Divider />
      <ItemList list={list.slice(0, 12)} />
      <Header as="h3" style={{ paddingTop: 80, marginBottom: 40 }}>베스트 분양</Header>
      <Divider />
      <ItemList list={list.slice(12, 24)} />
      <Header as="h3" style={{ paddingTop: 80, marginBottom: 40 }}>실시간 분양</Header>
      <Divider />
      <ItemList list={list.slice(24, 60)} />
    </div>
  )
}
ItemList.js
/* eslint-disable @next/next/no-img-element */
/* eslint-disable jsx-a11y/alt-text */
import { Grid } from 'semantic-ui-react'
import styles from "./ItemList.module.css";
import Link from 'next/link'
const ItemList = ({ list }) => {
    function temperament(item) {
        if (item !== undefined) {
            let temp = item.temperament.split(",");
            return (`${temp[0]}, ${temp[1]}, ${temp[2]}`);
        } else {
            return null;
        }
    }
    return (
        <div>
            {/* divided */}
            <Grid columns={3}  >
                <Grid.Row >
                    {list.map((item) => (
                        <Grid.Column key={item.id} alt={item.name}>
                            <Link href={`/view/${item.name}`}>
                                <a>
                                    <div className={styles.wrap}>
                                        {/* <img src={item.image_link} alt={item.name} className={styles.img_item} /> */}
                                        <img src={item.image.url} alt={item.name} className={styles.img_item} />
                                        <strong className={styles.tit_item}>{item.name}</strong>
                                        <span className={styles.txt_info}><span className={styles.bold}>품종</span> : {item.bred_for}</span>
                                        <span className={styles.txt_info}>
                                            몸무게 : {item.weight.metric}kg, 수명 : {item.life_span}
                                        </span>
                                        <strong className={styles.txt_info}>기질: {temperament(item)}</strong>
                                    </div>
                                </a>
                            </Link>
                        </Grid.Column>
                    ))}
                </Grid.Row>
            </Grid>
        </div >
    );
};
export default ItemList;
ItemList.module.css
.wrap{
    padding-bottom: 20px;
    text-align: center;
}
.img_item{
    display: block;
    margin: 0 auto;
    max-width: 320px;
    aspect-ratio: 16 / 9;
    max-height: 180px;
}
.tit_item{
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    width: 160px;
    margin: 10px auto;
}
.txt_info{
    display: block;
    margin-bottom: 10px;
    color: #999;
    text-align: left;
}
.num_price{
    font-size: 17px;
    color: #00bcd4;
    font-weight: bold;
}
.bold{
    font-weight: bold;
}
[id].js
import { useRouter } from 'next/router'
import Axios from 'axios';
import { useEffect, useState } from 'react';
import API_KEY from './../../ApiKey';
import Item from './../../src/component/Item';
const Post = () => {
    const router = useRouter()
    const { id } = router.query;
    const [item, setItem] = useState({});
    // const API_URL = `http://makeup-api.herokuapp.com/api/v1/products/${id}.json`;
    //thedogapi.com api 사용
    //https://docs.thedogapi.com/api-reference/breeds/breeds-list
    const API_URL = `https://api.thedogapi.com/v1/breeds/search`;
    function getData() {
        console.log("id : ", id);
        Axios.get(API_URL, {
            headers: {
                'x-api-key': API_KEY,
                Authorization: `token ${API_KEY}`
            },
            params: {
                name: id
            }
        }).then(res => {
            console.log("res.dat  : ", res.data);
            setItem(res.data)
        });
    }
    useEffect(() => {
        if (id && id !== undefined) {
            getData();
        }
    }, [id]);
    return <Item item={item[0]} />
}
export default Post
Item.js
/* eslint-disable @next/next/no-img-element */
import { Button } from 'semantic-ui-react';
import styles from './Item.module.css';
/*
bred_for: "Small rodent hunting, lapdog"
breed_group: "Toy"
height: {imperial: '9 - 11.5', metric: '23 - 29'}
id: 1
life_span: "10 - 12 years"
name: "Affenpinscher"
origin: "Germany, France"
reference_image_id: "BJa4kxc4X"
temperament: "Stubborn, Curious, Playful, Adventurous, Active, Fun-loving"
weight: {imperial: '6 - 13', metric: '3 - 6'}
*/
const Item = ({ item }) => {
    return (
        <>{item !== undefined &&
            <>
                <div className={styles.wrap}>
                    <div className={styles.img_item}>
                        <img src={`https://cdn2.thedogapi.com/images/${item.reference_image_id}.jpg`} alt={item.name} />
                    </div>
                    <div className={styles.info_item}>
                        <strong className={styles.tit_item}>{item.name}</strong>
                    </div>
                    <div className={styles.info_item}>
                        <Button color="orange">분양 받기</Button>
                    </div>
                </div>
                <div className={styles.wrap2}>
                    {item.bred_for && <div className={styles.txt_info}><span>품종:</span>{item.bred_for}</div>}
                    {item.breed_group && <div className={styles.txt_info}><span>품종 그룹:</span>{item.breed_group}</div>}
                    <div className={styles.txt_info}><span>몸무게:</span> {item.weight.metric}kg</div>
                    <div className={styles.txt_info}><span>신장:</span> {item.height.metric}cm</div>
                    <div className={styles.txt_info}><span>수명 :</span> {item.life_span}</div>
                    {item.origin && <div className={styles.txt_info}><span>혈통 :</span> {item.origin}</div>}
                    <div className={styles.txt_info}><span>기질: </span>{item.temperament}</div>
                </div>
            </>
        }
        </>
    );
};
export default Item;
Item.module.css
.wrap {
  margin-top: 10px;
  display: flex;
  padding: 10px 0 20px 0x; 
  /* border-bottom: 1px solid #ccc; */
}
.wrap2 {
  padding: 10px 0 40px 0x; 
  border-bottom: 1px solid #ccc; 
}
.img_item {
  flex: 70% 0 0;
}
.img_item img {
  display: block;
  width: 80%;
  height: auto;
  aspect-ratio: 16/9;
  box-shadow: 5px 5px 5px rgba(0, 0, 0,0.2);  
}
.info_item {
  display: flex;
  justify-content: center;
  align-items: center;
}
.tit_item {
  display: block;
  font-size: 2.2rem;
  margin-right: 20px;
  line-height: 1.5;
}
.num_price {
  display: block;
  font-size: 34px;
  margin-top: 20px; 
  color: #00bcd4;
}
.txt_info {
  display: block;
  font-size: 17px;
  margin: 20px 0 40px;
}
.txt_info span{
    margin-right: 20px;
}
3.서버사이드 렌더링 (Server-side Rendering/SSR/Dynamic Rendering)
Next js 모든 페이지 사전 레더링  (Pre-rendering)
더 좋은 퍼포먼스
검색엔진최적화 (SEO)
1.정적 생성
2.Server Side Rendering (SSR, Dynamic Rendering)
차이점은 언제 html 파일을 생성하는 가
[정적 생성]
- 프로젝트가 빌드하는 시점에 html 파일들이 생성
- 모든 요청에 재사용
- 퍼포먼스 이유로, 넥스트 js 는 정적 생성을 권고
- 정적 생성된 페이지들은 CDN에 캐시
- getStaticProps / getStaticPaths
[서버사이드 렌더링]은 매 요청마다 html 을 생성
- 항상 최신 상태 유지
- getServerSideProps
 
next.js는 프리렌더링(pre-rendering) 기능을 제공합니다.
말 그대로 사전에 미리 html을 렌더링 한다는 것인데요 즉 html을 미리 생성하고 최소한의js를 연결시킨 후 클라이언트에서 요청이 들어오면 해당 html을 로드하면서 나머지js를 불러와 화면에 렌더링 시켜주는 것이죠.
next.js는 주로 두가지 프리렌더링 방법을 제공합니다
- Server-side rendering: SSR 
- Static site rendering: SSG
next.js의 프리렌더링 으로 각 page에 getServerSideProps ,getStaticProps, getStaticPaths 를 사용해서 데이터를 가져올수 있습니다
요청에 따라 페이지가 데이터를 계속 업데이트해야한다 ! 그러면 getServerSideProps 를 사용하자!
요청이 들어와도 데이터의 변화는 없고 미리 렌더링해두어도 괜찮다 그러면 getStaticProps 를 사용하자! 
 
1. getServerSideProps (SSR)
getServerSideProps 는 요청할때마다 html이 생성되기 때문에 데이터가 계속 업데이트 됩니다.
요청할때마다 데이터를 계속 불러오는 것이죠.
그래서 데이터를 새로 받아오면 그 데이터로 페이지가 렌더링 됩니다.
//page
function Page({ data }) {
...
}
export async function getServerSideProps() {
  const res = await axios.get(`https://localholst:3065/user`)
  const data = res.data
  return { props: { data } }
}
page를 사용자가 요청하면 getServerSideProps 를 먼저 실행후 프론트가 서버에 직접요청 후 데이터를 받아와서 page 컴포넌트에 date를 props로 전달하여 렌더링 할 수 있습니다.
getServerSideProps 는 계속 데이터가 바뀌어야하는 페이지의 경우 사용합니다. 
2. getStaticProps, getStaticPaths (SSG)
html이 빌드타임에 생성됩니다.
빌드할때 데이터를 가져와서 html 을 생성후 사용자의 요청이 들어올때마다 빌드된 html 을 재사용합니다.
//pages/users/[id].js
function Page({ data }) {
 const router = useRouter()
  if (router.isFallback) {
    return <div>Loading...</div>
  }
...
}
export async function getStaticPaths() {
  const posts = await axios.get("https://jsonplaceholder.typicode.com/posts");
  const paths = posts.map(({ id }) => ({ params: { id: `${id}` } }));
// params: {id : '1'},{id : '2'}...
  return {
    paths,
    fallback: true,
  };
}
export async function getStaticProps() {
  const res = await axios.get(`https://localholst:3065/user`)
  const data = res.data
  return { props: { data } }
}
출처 :https://chaeyoung2.tistory.com/53
[id].js
import { useRouter } from 'next/router'
import Axios from 'axios';
import { useEffect, useState } from 'react';
import API_KEY from './../../ApiKey';
import Item from './../../src/component/Item';
import { Loader } from 'semantic-ui-react';
import Head from 'next/head';
const Post = ({ item }) => {
    // const router = useRouter()
    // const { id } = router.query;
    // const [item, setItem] = useState({});
    // const [isLoading, setIsLoading] = useState(true);
    // // const API_URL = `http://makeup-api.herokuapp.com/api/v1/products/${id}.json`;
    // //thedogapi.com api 사용
    // //https://docs.thedogapi.com/api-reference/breeds/breeds-list
    // const API_URL = `https://api.thedogapi.com/v1/breeds/search`;
    // function getData() {
    //     //console.log("id : ", id);
    //     Axios.get(API_URL, {
    //         headers: {
    //             'x-api-key': API_KEY,
    //             Authorization: `token ${API_KEY}`
    //         },
    //         params: {
    //             name: id
    //         }
    //     }).then(res => {
    //         setItem(res.data);
    //         setIsLoading(false);
    //     });
    // }
    // useEffect(() => {
    //     if (id && id !== undefined) {
    //         getData();
    //     }
    // }, [id]);
    return (
        <>
            {/* {isLoading && <div style={{ padding: "300px 0" }}>
                <Loader inline="centered" active>Loading</Loader>
            </div>
            }
            {!isLoading && <Item item={item[0]} />} */}
            {item &&
                <>
                    <Head>
                        <title>{item[0].name}</title>
                        <meta name="description" content={item[0].bred_for}></meta>
                    </Head>
                    <Item item={item[0]} />
                </>
            }
        </>
    )
}
export default Post;
export async function getServerSideProps(context) {
    const id = context.params.id
    const API_URL = `https://api.thedogapi.com/v1/breeds/search?name=${id}`;
    const res = await Axios.get(API_URL, {
        headers: { 'x-api-key': API_KEY, Authorization: `token ${API_KEY}` }
    });
    const data = res.data;
    return {
        props: {
            item: data
        }
    }
}
4.에러 페이지, 환경 변수 (Custom Error Page, Environment Variables)
Gnb.js
import { Menu } from 'semantic-ui-react'
import { useRouter } from 'next/router';
const Gnb = () => {
    const router = useRouter();
    let activeItem;
    if (router.pathname === "/") {
        activeItem = "home";
    } else if (router.pathname === "/about") {
        activeItem = "about";
    }
    function goLink(e, data) {
        if (data.name === "home") {
            router.push("/");
        } else if (data.name === "about") {
            router.push("/about");
        }
    }
    return (
        <Menu inverted>
            <Menu.Item name='home' active={activeItem === 'home'} onClick={goLink} />
            <Menu.Item name='about' active={activeItem === 'about'} onClick={goLink} />
            <Menu.Item name='Contact Us' active={activeItem === 'contact'}
                onClick={() => {
                    router.push("/contact");
                }}
            />
        </Menu>
    );
};
export default Gnb;
*에러페이지 설정
개발 모드 실행이 아니라 빌드 모드 실행 해서 500 에러 페이지 확인
$ npm run build
$ npm start
nextjs 공식 문서 500 : https://nextjs.org/docs/advanced-features/custom-error-page#500-page
nextjs 에서 기본적으로 에러페이지는 표시되며, 커스텀 페이지를 만들기 위해서는
pages 디렉토리에 다음과 같은 파일 명으로 404.js, _error.js 파일을 생성 하면 끝이다.
1 )404.js

import { Icon } from 'semantic-ui-react';
export default function Error404() {
    return (
        <div style={{
            padding: "200px 0", textAlign: "center", fontSize: 30
        }}>
            <Icon name="warning circle" color='red' />
            404 : 페이지를 찾을 수 없습니다.
        </div>
    );
}
2)_error.js

function Error({ statusCode }) {
    return (
        <p>
            {statusCode
                ? `An error ${statusCode} occurred on server`
                : 'An error occurred on client'}
        </p>
    )
}
Error.getInitialProps = ({ res, err }) => {
    const statusCode = res ? res.statusCode : err ? err.statusCode : 404
    return { statusCode }
}
export default Error
* 환경 변수 설정
root 디렉토리에 다음과 같은 두개 파일 생성

1).env.production
name=DEVELOPMENT NEXT_PUBLIC_NAME=DEVELOPMENT NEXT_PUBLIC_MAKEUP_API_URL=http://makeup-api.herokuapp.com/api/v1/products.json?brand=maybelline NEXT_PUBLIC_DOG_API_URL=https://api.thedogapi.com/v1/breeds
2).env.development
name=PRODUCTION NEXT_PUBLIC_NAME=PRODUCTION NEXT_PUBLIC_MAKEUP_API_URL=http://makeup-api.herokuapp.com/api/v1/products.json?brand=dior NEXT_PUBLIC_DOG_API_URL=https://api.thedogapi.com/v1/breeds
사용법
1) nodejs 환경 에서는 process.env.변수명 으로 사용
[id].js 파일 예
process.env.name 와 같이 접근한다
//nodejs 환경
export async function getServerSideProps(context) {
    const id = context.params.id
    const API_URL = `https://api.thedogapi.com/v1/breeds/search?name=${id}`;
    const res = await Axios.get(API_URL, {
        headers: { 'x-api-key': API_KEY, Authorization: `token ${API_KEY}` }
    });
    const data = res.data;
    return {
        props: {
            item: data,
            name: process.env.name
        }
    }
}
2) browser 환경에서는 process.env.NEXT_PUBLIC_변수명 으로 사용한다.
index.js 파일 예
process.env.NEXT_PUBLIC_NAME 로 사용
  const API_URL = process.env.NEXT_PUBLIC_DOG_API_URL;
  const ENV = process.env.NEXT_PUBLIC_NAME;
  const API_KEY = ApiKey;
  console.log("프젝트환경 : ", ENV, " , API_URL : ", API_URL);
5.정적 생성(Static Generation) - getStaticProps, getStaticPaths
Pre-rendering(사전 렌더링) 기본적으로 모든 페이지 pre-render 사전에 HTML 파일들을 만든다는 의미 퍼포먼스 향상,
SEO
Pre-rendering(사전 렌더링)
Static Generation : 정적 생성
Server-side Rendering  : 서버 사이드 렌더링
 
Static Generation : 정적 생성
- 마케팅 페이지
- 블로그 게시물
- 제품 목록
- 도움말 , 문서
Server-side Rendering
- 항상 최신 상태 유지
- 관리자 페이지
- 분석 차트




Static Generation : 정적 생성으로 설정한 페이지는
npm run build 시 html 파일들이 생성되거나 또는 운영중에 해당 페이지 로딩 및 접속시 html 파일들이 생성 된다.
한번 html 파일들에 의해 다음 접속시에는 빠르게 해당 페이지를 접속할 수 있게 된다.

예1) Server-side Rendering
index.js
/* eslint-disable react-hooks/exhaustive-deps */
import axios from 'axios';
import Head from 'next/head'
import Image from 'next/image'
import { useEffect, useState } from 'react';
import styles from '../styles/Home.module.css'
import ItemList from './../src/component/ItemList';
import { Header, Divider, Loader } from 'semantic-ui-react';
import ApiKey from '../ApiKey.js';
export default function Home({ list }) {
  return (
    <div>
      <Head>
        <title>Home | 강아지 분양소 | 도그파크</title>
        <meta name="description" content='도그 파크 홈입니다.' ></meta>
      </Head>
      <>
        <Header as="h3" style={{ paddingTop: 40, marginBottom: 40 }}>VIP 분양</Header>
        <Divider />
        <ItemList list={list.slice(0, 12)} />
        <Header as="h3" style={{ paddingTop: 80, marginBottom: 40 }}>베스트 분양</Header>
        <Divider />
        <ItemList list={list.slice(12, 24)} />
        <Header as="h3" style={{ paddingTop: 80, marginBottom: 40 }}>실시간 분양</Header>
        <Divider />
        <ItemList list={list.slice(24, 60)} />
      </>
    </div>
  )
}
//nodejs 환경
export async function getServerSideProps(context) {
  const API_URL = process.env.NEXT_PUBLIC_DOG_API_URL;
  const API_KEY = ApiKey;
  const res = await axios.get(API_URL, {
    headers: { 'x-api-key': API_KEY, Authorization: `token ${API_KEY}` }
  });
  const data = res.data;
  return {
    props: {
      list: data,
      name: process.env.name
    }
  }
}
예2 ) Static Generation : 정적 생성
cosmetics.js
/* eslint-disable react-hooks/exhaustive-deps */
import axios from 'axios';
import Head from 'next/head'
import CosmeticsItemList from '../src/component/CosmeticsItemList';
import { Header, Divider, Loader } from 'semantic-ui-react';
/**
 * 정적 페이지 테스트
 * @param {*} param0 
 * @returns 
 */
export default function Cosmetics({ list }) {
    console.log(list);
    return (
        <div>
            <Head>
                <title>Home | 강아지 분양소 | 도그파크</title>
                <meta name="description" content='도그 파크 홈입니다.' ></meta>
            </Head>
            <>
                <Header as="h3" style={{ paddingTop: 40 }}>
                    베스트 상품
                </Header>
                <Divider />
                <CosmeticsItemList list={list.slice(0, 9)} />
                <Header as="h3" style={{ paddingTop: 40 }}>
                    신상품
                </Header>
                <Divider />
                <CosmeticsItemList list={list.slice(9)} />
            </>
        </div>
    )
}
export async function getStaticProps() {
    const apiUrl = process.env.apiUrl;
    const res = await axios.get(apiUrl);
    const data = res.data;
    return {
        props: {
            list: data,
            neme: process.env.name
        }
    }
}
//740, 730, 729 3개의 아이디만 정적인 html 파일로 컴파일 되어 보여 줌
//fallback false 740, 730, 729 아닌 아이디는 404 페이지를 보여 줌
detail/[id].js
import Axios from "axios";
import Head from "next/head";
import Item from "../../src/component/CosmeticsItem";
const Post = ({ item, name }) => {
    return (
        <>
            {item && (
                <>
                    <Head>
                        <title>{item.name}</title>
                        <meta name="description" content={item.description}></meta>
                    </Head>
                    {name} 환경 입니다.
                    <Item item={item} />
                </>
            )}
        </>
    );
};
export default Post;
export async function getStaticPaths() {
    //740, 730, 729 3개의 아이디만 정적인 html 파일로 컴파일 되어 보여 줌
    //fallback false 740, 730, 729 아닌 아이디는 404 페이지를 보여 줌
    return {
        paths: [
            { params: { id: "740" } },
            { params: { id: "730" } },
            { params: { id: "729" } },
        ],
        fallback: true,
    };
}
export async function getStaticProps(context) {
    const id = context.params.id;
    const apiUrl = `http://makeup-api.herokuapp.com/api/v1/products/${id}.json`;
    const res = await Axios.get(apiUrl);
    const data = res.data;
    return {
        props: {
            item: data,
            name: process.env.name,
        },
    };
}
6.isFallback, getStaticPaths
detail/[id].js
import Axios from "axios";
import Head from "next/head";
import Item from "../../src/component/CosmeticsItem";
import { useRouter } from 'next/router';
import { Loader } from "semantic-ui-react";
const Post = ({ item, name }) => {
    const router = useRouter();
    //console.log("isFallback : ", router.isFallback)
    //로딩시 fallback 은 처음 true 였다가  false  변경 처리된다.
    //isFallback :  true
    //isFallback: false
    if (router.isFallback) {
        return (
            <div style={{ padding: "100px 0" }}>
                <Loader active inline="centered">
                    Loading
                </Loader>
            </div>
        )
    }
    return (
        <>
            {item && (
                <>
                    <Head>
                        <title>{item.name}</title>
                        <meta name="description" content={item.description}></meta>
                    </Head>
                    {name} 환경 입니다.
                    <Item item={item} />
                </>
            )}
        </>
    );
};
export default Post;
export async function getStaticPaths() {
    //740, 730, 729 3개의 아이디만 정적인 html 파일로 컴파일 되어 보여 줌
    //fallback false 740, 730, 729 아닌 아이디는 404 페이지를 보여 줌
    const apiUrl = process.env.apiUrl;
    const res = await Axios.get(apiUrl);
    const data = res.data;
    return {
        //  paths: [
        //     { params: { id: "740" } },
        //     { params: { id: "730" } },
        //     { params: { id: "729" } },
        // ],
        paths: data.slice(0, 9).map((item) => ({
            params: { id: item.id.toString() }
        })),
        fallback: true,
    };
}
export async function getStaticProps(context) {
    const id = context.params.id;
    const apiUrl = `http://makeup-api.herokuapp.com/api/v1/products/${id}.json`;
    const res = await Axios.get(apiUrl);
    const data = res.data;
    return {
        props: {
            item: data,
            name: process.env.name,
        },
    };
}
7.API Routes, 로그인 구현


pages/login.js

import { Button, Form } from "semantic-ui-react"
import Axios from 'axios';
import { useRouter } from 'next/router';
const Login = () => {
    const router = useRouter();
    function login() {
        Axios.post('/api/login', {
        }).then(res => {
            if (res.status === 200) {
                //로그인 성공
                router.push("/admin");
            }
        })
    }
    return (
        <div style={{ padding: "100px 0", textAlign: "center" }} >
            <Form>
                <Form.Field inline>
                    <input placeholder="ID" />
                </Form.Field>
                <Form.Field inline>
                    <input type="password" placeholder="Password" />
                </Form.Field>
                <Button color="blue" onClick={login} >Login</Button>
            </Form>
        </div>
    );
    
};
export default Login;
pages/admin.js

import { useRouter } from 'next/router';
import Axios from 'axios';
import { useEffect, useState } from 'react';
import { Button } from 'semantic-ui-react';
const Admin = () => {
    const router = useRouter();
    const [isLogin, setIsLogin] = useState(false);
    function checkLogin() {
        Axios.get("/api/isLogin").then((res) => {
            if (res.status === 200 && res.data.name) {
                //로그인
                setIsLogin(true);
            } else {
                //로그인 
                router.push("/login");
            }
        });
    }
    function logout() {
        Axios.get("/api/MyLogout")
            .then(res => {
                if (res.status === 200) {
                    router.push("/");
                }
            })
    }
    useEffect(() => {
        checkLogin();
    }, []);
    return (
        <>admin
            {isLogin && <Button onClick={logout} >Logout</Button>}
        </>
    );
};
export default Admin;
pages/api/isLogin.js
export default function handler(req, res) {
  res.statusCode = 200;
  res.json({ name: req.cookies.a_name });
}
pages/api/login.js
export default function handler(req, res) {
  // res.statusCode = 200;
  // res.json({ name: null });
  if (req.method === "POST") {
    res.setHeader("Set-Cookie", "a_name=Mike;Max-Age=3600;HttpOnly,Secure");
    res.statusCode = 200;
    res.json({ message: "ok" });
  }
}
pages/api/MyLogout.js
/* eslint-disable import/no-anonymous-default-export */
export default (req, res) => {
  res.setHeader("Set-Cookie", "a_name=Mike;Max-Age=0;HttpOnly,Secure");
  res.statusCode = 200;
  res.json({ message: "ok" });
};
소스 : https://github.com/braverokmc79/nextjs-tutorial














댓글 ( 4)  
댓글 남기기