React

 

 

 

 

코딩앙마

 

강의 목록  :    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

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

샘물은 젖소가 먹으면 우유가 되고, 뱀이 먹으면 독이 된다.

댓글 ( 4)

댓글 남기기

작성