코딩앙마
강의 목록 : 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)
댓글 남기기