๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
2. Back-end/>> ๐Ÿ‘ Next.js

03-3 ํ™”๋ฉด๊ตฌ์„ฑ

by ๋ธ”๋ก๋ฉ”ํƒ€ 2022. 12. 22.
  1. ํ”„๋กœ์ ํŠธ ๋งŒ๋“ค๊ธฐ

 

ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค.  ๋จผ์ € ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค ํด๋”๋กœ ์ด๋™ ํ›„ ์•„๋ž˜ ๋ช…๋ น์„ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.  

ํ”„๋กœ์ ํŠธ๋ช…์€ insta-clone์œผ๋กœ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. (์ด๋ฆ„์€ ์›ํ•˜๋Š” ํ”„๋กœ์ ํŠธ ์ด๋ฆ„์œผ๋กœ  ์‚ฌ์šฉํ•ด๋„ ๋ฉ๋‹ˆ๋‹ค.) 

 

next.js ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ๋Š”   npx create-react-app  ์„ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค. 



> npx create-react-app  insta-clone   



1~2๋ถ„ํ›„์— ํ”„๋กœ์ ํŠธ๊ฐ€ ์ƒ์„ฑ์ด ๋ฉ๋‹ˆ๋‹ค.  ์ด์ œ ๋น„์ฃผ์–ผ ์ŠคํŠœ๋””์˜ค ์ฝ”๋“œ๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ์—ด์–ด ๋ด…์‹œ๋‹ค.

 

> ๋น„์ฃผ์–ผ์ŠคํŠœ๋””์˜ค ์ฝ”๋“œ์—์„œ ์ƒ์„ฑํ•œ insta-clone ํด๋”๋ฅผ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

 

์ž๋™ ์ƒ์„ฑ๋œ ์ฝ”๋“œ๋ฅผ ์ผ๋‹จ ์‹คํ–‰ํ•ด ๋ด…์‹œ๋‹ค. 

 

๋น„์ฃผ์–ผ์ŠคํŠœ๋””์˜ค ์ฝ”๋“œ(VSC) ๋ฉ”๋‰ด์—์„œ Terminal - New Terminal ์„ ์„ ํƒ ํ•œ ํ›„  npm run dev๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. 

 

ํฌ๋กฌ์„ ์—ด์–ด์„œ http://localhost:3000/ ์ž…๋ ฅํ•˜๋ฉด Welcome to Next.js! ๊ฐ€ ๋œจ๋Š”๊ฑธ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

 

[๋””๋ ‰ํ† ๋ฆฌ ์—ญํ• ]

pages ํด๋” :  ํŽ˜์ด์ง€ ์ƒ์„ฑ, ๋ผ์šฐํŒ… ์ฒ˜๋ฆฌ๊ฐ€ ๋จ 

public  ํด๋” : ์ด๋ฏธ์ง€,๋น„๋””์˜ค,์˜ค๋””์˜ค ๋ฆฌ์†Œ์Šค ํด๋”

pages/api ํด๋” : ์„œ๋ฒ„์—์„œ ๋™์ž‘ํ•˜๋Š” api ๊ตฌํ˜„ ํด๋”



tailwind๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. 

cmd> npm install -D tailwindcss postcss autoprefixer

 

ํ˜„์žฌ ํ”„๋กœ์ ํŠธ์— tailwindcss ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. 

cmd> npx tailwindcss init -p



tailwind.config.js ์ฝ”๋“œ๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค.  pages, components ๋””๋ ‰ํ† ๋ฆฌ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํ™•์žฅ์ž js,jsx,ts,tsx๋ฅผ ์ •์˜ํ•œ ๋‚ด์šฉ์ž…๋‹ˆ๋‹ค. 

 

 

module.exports = {

  content: [

    "./pages/**/*.{js,jsx,ts,tsx}",

    "./components/**/*.{js,jsx,ts,tsx}",

  ],

  theme: {

    extend: {},

  },

  plugins: [],

};




styles/globals.css ์ˆ˜์ •

 

@tailwind base;

@tailwind components;

@tailwind utilities;



pages/index  ์ž๋™์œผ๋กœ ์ž‘์„ฑ๋œ ์ฝ”๋“œ๋ฅผ ์ง€์šฐ๊ณ  ์ธ์Šคํƒ€ํด๋ก  Start!!! ์ฝ”๋“œ๋กœ ์ˆ˜์ •ํ•ด ๋ด…์‹œ๋‹ค.

 

2์žฅ์—์„œ ๋ฐฐ์šด Tailwind CSS  (m-3, flex, justify-between, w-100 h-8 shadow rounded …) ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•ด ๋ด…์‹œ๋‹ค.  



export default function Home() {

  return (

    <div className="m-3">

      <div className="flex justify-between  m-3">

        <div className="w-100 h-8 shadow rounded 

         bg-[#d51c7c] text-white">์ธ์Šคํƒ€ํด๋ก  Start!!!</div>

      </div>

    </div>

  )

}

 

์„œ๋ฒ„๊ฐ€ ์‹คํ–‰๋˜์–ด ์žˆ๋‹ค๋ฉด Ctrl+C ๋กœ ์„œ๋ฒ„๋ฅผ ์ค‘์ง€ ํ›„ npm run dev๋ฅผ ์‹คํ–‰์‹œ์ผœ ์ฃผ์„ธ์š”. 

(Tailwind CSS ์„ค์ •์„ ์œ„ํ•ด์„œ tailwind.config.js ๋ณ€๊ฒฝํ•˜์˜€์œผ๋ฏ€๋กœ ์žฌ์‹คํ–‰์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.)

 

Tailwind CSS๊ฐ€ ์ ์šฉ๋œ ํ™”๋ฉด์œผ๋กœ ์ถœ๋ ฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. 

Tailwind CSS๊ฐ€ ์ ์šฉ๋˜๋Š”๊ฒƒ๋„ ํ™•์ธํ–ˆ๊ณ , ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์ž‘์—…์„ ์ง„ํ–‰ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. 

 

component ๋ผˆ๋Œ€ ๋งŒ๋“ค๊ธฐ

 

ํ™”๋ฉด์€ ํฌ๊ฒŒ  Header, Feed, Modal ๋กœ ๋‚˜๋ˆ„๊ฒ ์Šต๋‹ˆ๋‹ค.  ๋‚˜๋ˆ„์–ด์ง„ Header, Feed, Modal์€ components ์— ์ •์˜ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.  

 

component๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š” ํ•˜๋‚˜์˜ ํŽ˜์ด์ง€์— html์„ ๋งŒ๋“ค์–ด ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ๊ธธ์–ด์ง€๊ณ  ๊น”๋”ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์—  ํ•ด๋‹น ๊ธฐ๋Šฅ๋ณ„๋กœ ์ปดํฌ๋„ŒํŠธํ™” ์‹œํ‚ค๋Š”๊ฑธ ๊ถŒ์žฅํ•ฉ๋‹ˆ๋‹ค. 

 

ํ•˜๋‚˜์”ฉ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. 

const Home: NextPage = () => {

  return (

    <div className=”bg-gray-50 h-screen overflow-y-scroll scrollbar-hide”> 

      {/* Header */}

 

      {/* Feed */}

 

      {/* Modal */}

    </div>

  )

}



bg-gray-50           background ์นผ๋ผ ํšŒ์ƒ‰ 50 

h-screen               height 100vh (viewport ๋†’์ด์˜ 100%) 

overflow-y-scroll   ํฌ๊ธฐ๊ฐ€ overflow ๋˜์—ˆ์„๋•Œ y-scroll ํ‘œ์‹œ 

 

  1. Header Component 

๋จผ์ €, components ํด๋”๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ทธ ๋ฐ‘์— Header.tsx๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. 

 

rfce ์ž…๋ ฅ ํ›„ ํƒญ์„ ์„ ํƒํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ์ž๋™์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. 

 

( ๋งŒ์•ฝ, ์ž๋™์ƒ์„ฑ์ด ๋˜์ง€ ์•Š์œผ๋ฉด VS Code ์ต์Šคํ…์…˜์—์„œ ES7 React/Redux/GraphQL/React-Native snippets ์„ค์น˜ํ•ด ์ฃผ์„ธ์š”. ๋ฐ˜๋ณต๋˜๋Š” ์ž‘์—…์„ ์ค„์—ฌ์ฃผ๊ณ  ๊ฐœ๋ฐœ์˜ ์ƒ์‚ฐ์„ฑ์„ ๋„์™€์ค๋‹ˆ๋‹ค.)

 

import React from 'react'

 

function Header() {

    return (

        <div>

            Header

            {/* Left */}

 

            {/* Middle */}

 

            {/* Right */}

        </div>

    )

}

 

export default Header




Header๋Š” ํฌ๊ฒŒ Left, Middle, Right๋กœ ๋‚˜๋ˆ„์–ด์ง‘๋‹ˆ๋‹ค. 

Left์—๋Š” ์ธ์Šคํƒ€๊ทธ๋žจ๋กœ๊ณ ๋ฅผ ํ‘œ์‹œํ•˜๊ณ  ๊ฐ€์šด๋ฐ๋Š” ์„œ์น˜ Input Form , ๊ทธ๋ฆฌ๊ณ  ์˜ค๋ฅธ์ชฝ์—๋Š” ๋ฉ”๋‰ด์•„์ด์ฝ˜์„ ํ‘œ์‹œํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. 



      Left (์ด๋ฏธ์ง€)                       Middle (Search)                                        Right (icons)



Left์— ์ด๋ฏธ์ง€๋ฅผ ์ถ”๊ฐ€ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.  

 

<Image

    src='https://links.papareact.com/ocw'

    layout='fill'

    objectFit='contain'

/>

 

Image ํด๋ž˜์Šค์˜ object-fit์€ <img>, <video>์˜ ์‚ฌ์ด์ฆˆ๊ฐ€ ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€์—์„œ ์–ด๋–ป๊ฒŒ ์กฐ์ ˆ๋˜๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. object-fit์ด ‘contain’์ด๋ฉด ๋‚ด๋ถ€ ์ฝ˜ํ…์ธ ์˜ ๋น„์œจ์„ ์œ ์ง€ํ•˜๋ฉด์„œ ์ปจํ…Œ์ด๋„ˆ์˜ fit ๋˜๋„๋ก ๋‚ด๋ถ€ ์ฝ˜ํ…์ธ ์˜ ์‚ฌ์ด์ฆˆ๊ฐ€ ์กฐ์ •๋ฉ๋‹ˆ๋‹ค. 

 

 

Image ํด๋ž˜์Šค์— src, layout, objectFit๋ฅผ ์ž…๋ ฅํ•œ ํ›„ ์‹คํ–‰ํ•ด ๋ณด์•˜์Šต๋‹ˆ๋‹ค. 

 

์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋„ค์š”. 

Next/image์—์„œ๋Š” src์— ๋“ค์–ด๊ฐ€๋Š” ๋„๋ฉ”์ธ์„ ๋ฏธ๋ฆฌ next.config.js์— ์ •์˜๋ฅผ ํ•ด ์ฃผ์–ด์•ผ ๋ฉ๋‹ˆ๋‹ค. ์ •์ ํŒŒ์ผ์˜ ์ ‘๊ทผ์€ ์ƒ๊ด€์—†์ง€๋งŒ ์™ธ๋ถ€ ์„œ๋ฒ„ ์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์™€์•ผ ๋  ๊ฒฝ์šฐ๋Š” ๋„๋ฉ”์ธ ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.  

 

next.config.js์— ๋„๋ฉ”์ธ์„ ์ถ”๊ฐ€ํ•ด ์ฃผ์„ธ์š”.  

์„œ๋ฒ„๋ฅผ ์ค‘์ง€ํ•˜๊ณ  ๋‹ค์‹œ npm run dev๋กœ ์‹คํ–‰ํ•ด ์ฃผ์„ธ์š”. ํ™˜๊ฒฝํŒŒ์ผ ๋ณ€๊ฒฝ์‹œ์—๋Š” ๋ฐ˜๋“œ์‹œ ์„œ๋ฒ„๋ฅผ ์ค‘์ง€ํ•˜๊ณ  ์‹คํ–‰ํ•˜์…”์•ผ ๋ฉ๋‹ˆ๋‹ค. 



์ธ์Šคํƒ€๊ทธ๋žจ ๋กœ๊ณ  ์ด๋ฏธ์ง€๊ฐ€ ์ถœ๋ ฅ๋˜์—ˆ๋„ค์š”.  ํ•œ ๊ฐ€์šด๋ฐ์— ํฌ๊ฒŒ ํ‘œ์‹œ๋˜์—ˆ๋„ค์š”. ์œ„์น˜๋‚˜ ํฌ๊ธฐ๋Š” Tailwind๋กœ ์กฐ์ •ํ•ด ๋ณผ๊ฒŒ์š” 

 

relative h-24 2-24๋กœ ์‚ฌ์ด์ฆˆ๋ฅผ ๋ณ€๊ฒฝํ•˜์˜€๊ณ , ์ƒ์œ„ div์— flex justify-between ๋ฅผ ์ถ”๊ฐ€ํ•˜์˜€์Šต๋‹ˆ๋‹ค.

 

์ด๋ฏธ์ง€๋Š” ๋ชจ๋ฐ”์ผ๋•Œ์™€ PCํ™”๋ฉด์ผ๋•Œ 2๊ฐ€์ง€๋กœ ํ‘œ์‹œํ•˜๋ ค๊ณ  ํ•ด์š”. ํ˜„์žฌ ์ด๋ฏธ์ง€๋Š” PC๋ฒ„์ „์ผ๋•Œ๋งŒ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.  ๊ทธ๋ž˜์„œ hidden lg:inline-grid ๋ฅผ ๋„ฃ์–ด ์ค๋‹ˆ๋‹ค. 

 

์ด์ œ ๋ชจ๋ฐ”์ผ๋•Œ ์ด๋ฏธ์ง€๋กœ ์ถ”๊ฐ€ํ•ด ๋ด…์‹œ๋‹ค. 

์ด๋ฏธ์ง€๋ฅผ ์ถ”๊ฐ€ํ›„ className์— relative w-10 h-10 lg:hidden ์„ ๋„ฃ์–ด ์ฃผ์—ˆ์Šต๋‹ˆ๋‹ค. 

 

์•„์ด์ฝ˜์€ heroicons ๋ฌด๋ฃŒ ์•„์ด์ฝ˜์„ ์‚ฌ์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.  ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋จผ์ €, ์ธ์Šคํ†จ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. 

 

heroicons ์ธ์Šคํ†จํ•˜๊ธฐ 

npm install @heroicons/react@v1  



tailwindcss๊ฐ€ ์ง€์›ํ•˜๋Š” forms ๋„ ๊ฐ™์ด ์ธ์Šคํ†จ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.  ์ด ๋ถ€๋ถ„์€ ์ธ์Šคํ†จ ํ›„ ํ™˜๊ฒฝ์„ค์ •๋„ ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. 

 

npm install @tailwindcss/forms

 

์Šคํฌ๋กค๋ฐ”๋„ ํ•„์š”ํ•ด์„œ ๋ฏธ๋ž˜ ์ธ์Šคํ†จ ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. 

npm install –save-dev tailwind-scrollbar

 

tailwind.config.js  forms, scrollbar  ์ถ”๊ฐ€ํ•ด ์ฃผ์„ธ์š”. 

module.exports = {
 content: [
   './pages/**/*.{js,ts,jsx,tsx}',
   './components/**/*.{js,ts,jsx,tsx}',
   './app/**/*.{js,ts,jsx,tsx}',
 ],
 theme: {
   extend: {},
 },
 plugins: [require("@tailwindcss/forms")],
          require("tailwind-scrollbar"),
          require("tailwind-scrollbar-hide"),
 ],
}

 

Search ํ™”๋ฉด์ถ”๊ฐ€ 

               {/* Middle */}
               <div className="max-w-xs">
                   <div className="relative mt-1 p-3 rounded-md">
                       <div className="absolute inset-y-0 pl-3 flex items-center
                                   pointer-events-none">
                           <SearchIcon className="h-5 w-5" />
                       </div>
                       <input
                           className="bg-gray-50 block w-full pl-10
                                       sm:text-sm border-gray-300 rounded-md
                                       focus:ring-black focus:border-black"
                           type="text" placeholder="Search" />
                   </div>
               </div>

SearchIcon์€ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋Ÿฐ๋ฐ SearchIcon์€ ํฌ์ปค์Šค๊ฐ€ ํ•„์š”์—†๊ธฐ ๋•Œ๋ฌธ์— pointer-events-none์„ ์ƒ์œ„ div ํด๋ž˜์Šค๋กœ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

 

Menu ํ™”๋ฉด

           {/* Right */}
               <div className="flex items-center justify-end space-x-4">
                   <HomeIcon className="navBtn " />
                   <MenuIcon className="h-6 md:hidden cursor-pointer" />
                   <PaperAirplaneIcon className="navBtn rotate-45" />
                   <PlusCircleIcon className="navBtn" />
                   <UserGroupIcon className="navBtn" />
                   <HeartIcon className="navBtn" />
               </div>

MenuIcon์€ ํ™”๋ฉด์ด ์ปค์ง€๋ฉด (md) ํžˆ๋“ ์‹œํ‚ต๋‹ˆ๋‹ค.  navBtn์€ ๋ฒ„ํŠผ๋“ค์˜ ์†์„ฑ์ด ๊ฐ™๊ธฐ ๋•Œ๋ฌธ์— globals.css ์— ์ •์˜ํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

 

globals.css navBtn ํด๋ž˜์Šค๋ฅผ ์ •์˜ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

@layer components {
   .navBtn {
       @apply hidden h-6 md:inline-flex
       cursor-pointer hover:scale-125
       transition-all duration-150 ease-out;
   }
}

ํ—ค๋”๋ฅผ ์™„์„ฑํ–ˆ์Šต๋‹ˆ๋‹ค. 

 

 

Feed๋ฅผ ๊ตฌ์„ฑํ•ด ๋ด…์‹œ๋‹ค. 

 

 

Feed Component

 

Feed Component๋ฅผ ์ƒ์„ฑํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. 

 

components/Feed.tsx ๋งŒ๋“ค๊ธฐ

Feed๋Š” 2๊ฐœ์˜ ์„ธ์…˜์œผ๋กœ ๊ตฌ๋ถ„๋ฉ๋‹ˆ๋‹ค.  ์ฒซ๋ฒˆ์งธ ์„ธ์…˜์—๋Š” Stories, Posts ๋‘๋ฒˆ์งธ ์„ธ์…˜์—๋Š” Mini profile, Suggestions ์œผ๋กœ ๊ตฌ๋ถ„๋ฉ๋‹ˆ๋‹ค. 

 

Stories ์ปดํฌ๋„ŒํŠธ ์ž‘์„ฑ 

 

Stories์ปดํฌ๋„ŒํŠธ๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ Story์ปดํฌ๋„ŒํŠธ๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. 

import React from 'react'

function Stories() {
   return (
       <div>
           {/* Story */}
           {/* Story */}
           {/* Story */}
           {/* Story */}
           {/* Story */}
       </div>
   )
}

export default Stories

์ธ์Šคํƒ€์—์„œ ๋งจ์œ„์— ์œ„์น˜ํ•ด ์žˆ๊ณ , ์ธ์นœ๋“ค์ด ์Šคํ† ๋ฆฌ๋ฅผ ์˜ฌ๋ฆฌ๋ฉด ์•„์ด์ฝ˜์ด ํ•˜์ด๋ผ์ดํŠธ ์ฒ˜๋ฆฌ๋˜์–ด ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ์ธ์นœ๊ด€๋ จ๋ถ€๋ถ„์€ ์ด๋ฒˆ ๋ฒ”์œ„๊ฐ€ ์•„๋‹ˆ๋ฏ€๋กœ,  ์Šคํ† ๋ฆฌ์˜ ์ธ์นœ ํ‘œ์‹œ๋Š” randomuser.me ์‚ฌ์ดํŠธ์˜  API๋ฅผ ์ด์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. 



๋จผ์ €, next.js์—์„œ ์ง€์›ํ•˜๋Š” getServerSideProps์— ๋Œ€ํ•ด์„œ ์„ค๋ช…ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. 

 

getServerSideProps๋Š” Next์—์„œ ํŽ˜์ด์ง€์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์ œ๊ณต๋ฐ›๋Š” ๊ธฐ๋ณธ API์ž…๋‹ˆ๋‹ค. ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ํŒจ์น˜ํ•˜์—ฌ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•˜๋„๋ก ๊ตฌ์„ฑ์ด ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.    



randomUsers ๋กœ ์Šคํ† ๋ฆฌ์˜ ์œ ์ €๋“ค์„ ์ž๋™์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. 

export async function getServerSideProps() {
 console.log("getServerSideProps  step1 ");
 let randomUsersResults = [];

 try {
    
   const res = await fetch(
     "https://randomuser.me/api/?results=30&inc=name,login,picture"
   );
   randomUsersResults = await res.json();

 } catch (e) {
   randomUsersResults = [];
 }

 return {
   props: {
     randomUsersResults,
   },
 };
}

getServerSideProps๋Š” ์ด๋ฆ„ ๊ทธ๋Œ€๋กœ ์„œ๋ฒ„ ์ธก์—์„œ props๋ฅผ ๋ฐ›์•„์˜ค๋Š” ๊ธฐ๋Šฅ์„ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ํŽ˜์ด์ง€๋ฅผ ์š”์ฒญ ์‹œ๋งˆ๋‹ค ์‹คํ–‰์ด ๋˜๋ฉฐ getServerSideProps์—์„œ ํŽ˜์ด์ง€๋กœ ์ „๋‹ฌํ•ด์ค€ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„์—์„œ ๋ Œ๋”๋ง์„ ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„์—์„œ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์—, ์•ž์˜ ์ฝ”๋“œ๋ฅผ ์ƒˆ๋กœ๊ณ ์นจ์„ ํ•˜์—ฌ ์‹คํ–‰ํ•ด ๋ณธ๋‹ค๋ฉด ๋ธŒ๋ผ์šฐ์ €์˜ ์ฝ˜์†” ์ถœ๋ ฅ์ด ์•„๋‹Œ ํ„ฐ๋ฏธ๋„์—์„œ ์ถœ๋ ฅ ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜์‹ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

 

์Šคํ† ๋ฆฌ์— ์Šคํฌ๋กค๋ฐ”๋„ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์•ž์—์„œ ๋ฏธ๋ฆฌ  tailwind-scrollbar๋ฅผ ์ธ์Šคํ†จ ํ–ˆ์Šต๋‹ˆ๋‹ค. 

 

Feed  ์†Œ์Šค

function Feed({ randomUsers }) {
   return (
       <main className="grid grid-cols-1 md:grid-cols-2
           md:max-w-3xl xl:grid-cols-3 xl:max-w-6xl max-auto">
           {/* Section */}
           <section className="col-span-2">
               <Stories randomUsers={randomUsers} />
               {/* Posts */}
           </section>

           {/* Section */}
           <section>
               {/* Mini profile */}
               {/* Suggestions */}
           </section>
       </main>
   )
}

Stories ์†Œ์Šค 

function Stories({ randomUsers }) {

   return (
       <div className="flex space-x-2 p-6 bg-white mt-8 
             border-gray-200 rounded-sm 
             overflow-x-scroll scrollbar-thin scrollbar-thumb-black">
           {randomUsers?.results?.map(profile => (
               <Story key={profile.login.username} 
                   img={profile.picture.thumbnail} 
                   username={profile.login.username} />
           ))}
       </div>
   )
}

username, thumbnail์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ์Šคํฌ๋กค ๊ด€๋ จ overflow-x-scroll scrollbar-thin scrollbar-thumb-black ๋„ ํด๋ž˜์Šค์— ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค. 

 

Story ์†Œ์Šค

function Story({key, img, username}) {
 return (
   <div>
       <img
           src={img}
           alt="profile pic"
           className="h-14 w-14 rounded-full p-[1.5px] border-red-500 border-2
               object-contail cursor-pointer hover:scale-110 
               transform duration-200 ease-out" 
       />
       <p className="text-xs w-14 truncate text-center">{username}</p>
   </div>
 )
}

hover:scale-110  ์Šคํ† ๋ฆฌ์— ๋งˆ์šฐ์Šค ์˜ค๋ฒ„์‹œ์— ์ด๋ฏธ์ง€๋ฅผ ํฌ๊ธฐ๋ฅผ 110%๋กœ ํ™•๋Œ€ํ•ฉ๋‹ˆ๋‹ค.

 

 

  Posts

function Posts() {
 return (
   <div>
       {/* Post */}
       {/* Post */}
       {/* Post */}
       {/* Post */}
   </div>
 )
}

Posts ์ปดํฌ๋„ŒํŠธ๋Š” ์—ฌ๋Ÿฌ๊ฐœ์˜ Post๋กœ ์ด๋ฃจ์–ด์ ธ ์žˆ์Šต๋‹ˆ๋‹ค. 

 

ํŒŒ์ด์–ด๋ฒ ์ด์Šค์™€ ์—ฐ๊ฒฐํ•˜๋Š” ๋ถ€๋ถ„์€ 5์žฅ์—์„œ ๋‹ค๋ฃจ๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.  ์—ฌ๊ธฐ์—์„œ๋Š” ๋”๋ฏธ๋ฐ์ดํ„ฐ๋กœ ํ™”๋ฉด์„ ๊ตฌ์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

const posts = [   // ๋”๋ฏธ ๋ฐ์ดํ„ฐ 
   {
       id: "123",
       username: "blockmeta",
       userImg: "https://i.pinimg.com/564x/dc/36/eb/
                   dc36eb1e90a9513ef82b343ef2d852ac.jpg",
       img: "https://i.pinimg.com/236x/63/b7/41/
                   63b7413ad91d74ae34986de68283bf9d.jpg",
       caption: "Next.js ์ธ์Šคํƒ€ํด๋ก  ์ฝ”๋”ฉ ์ •๋ณต",
   }
];

function Posts() {
 return (
   <div>
       {
           posts.map((post) => (
               <Post key={post.id} id={post.id}
                     username={post.username}
                     userImg={post.userImg}
                     img={post.img}
                     caption={post.caption}
               />
           ))
       }
   </div>
 )
}

id, username, userImg, img, caption ์„  props (ํ”„๋กฌ์Šค)๋กœ Post ์ปดํฌ๋„ŒํŠธ์— ๊ฐ’์„ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. 

 

๋”๋น„ ๋ฐ์ดํ„ฐ posts๋Š” ๋ฆฌ์ŠคํŠธ๋กœ ๋˜์–ด ์žˆ์–ด์„œ map()์„ ์ด์šฉํ•ฉ๋‹ˆ๋‹ค. 



Post์—์„œ๋Š” ์ „๋‹ฌ ๋ฐ›์€ props๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. 

 

function Post( { id, username, userImg, img, caption })  

์ „๋‹ฌํ• ๋•Œ๋‚˜ ๋ฐ›์„๋•Œ ๊ฐ™์€ ๋ณ€์ˆ˜์ด๋ฆ„์œผ๋กœ ์‚ฌ์šฉํ•˜๊ฒŒ์š”. 

 

Post์—๋Š” Header, img, Buttons, caption, comments, input box ํ•„์š”ํ•œ ์ปดํผ๋„ŒํŠธ๊ฐ€ ๋งŽ์ด ์žˆ์–ด์š”. ์šฐ๋ฆฌ๊ฐ€ ๊ตฌํ˜„ํ•  ํ•ต์‹ฌ ๊ธฐ๋Šฅ์ด ์žˆ์Šต๋‹ˆ๋‹ค. 

 

ํ•˜๋‚˜์”ฉ ์‚ดํŽด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

 

Header

       {/* Header */}
       <div className="bg-white my-7 border rounded-sm">
           <div className="flex items-center p-5">
               <img className="rounded-full h-12 w-12 object-contain
                                border p-1 mr-3" />
               <p className="flex-1 font-bold">{username}</p>
               <DotsHorizontalIcon className="h-5" />
           </div>
       </div>

Header ๋ถ€๋ถ„์ด ์‹คํ–‰๋œ ํ™”๋ฉด์ž…๋‹ˆ๋‹ค.

 

img 

 

<img src={img} className=”object-cover w-full” />

 

Button

       {/* Buttons */}
       <HeartIcon className='btn' />
       <ChatIcon className='btn' />
       <PaperAirplaneIcon className='btn' />

๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” btn ํด๋ž˜์Šค๋Š” globals.css์— @apply๋กœ ์ •์˜ํ•ด ์ค๋‹ˆ๋‹ค.  

 

globals.css

@tailwind base;
@tailwind components;
@tailwind utilities;

@layer components {
   .navBtn {
       @apply hidden h-6 md:inline-flex
       cursor-pointer hover:scale-125
       transition-all duration-150 ease-out;
   }

   .btn {
       @apply h-7 hover:scale-125 cursor-pointer
       transition-all duration-150 ease-out
   }
}

BookmarkIcon ๋„ ์ถ”๊ฐ€ ํ–ˆ์Šต๋‹ˆ๋‹ค.

       {/* Buttons */}
       <div className="flex justify-between px-4 pt-4">
           <div className="flex space-x-4">
               <HeartIcon className='btn' />
               <ChatIcon className='btn' />
               <PaperAirplaneIcon className='btn' />
           </div>
           <BookmarkIcon className="btn" />
       </div>

HeartIcon, ChatIcon, PaperAirplaneIcon ์ด ํ‘œ์‹œ๋œ ํ™”๋ฉด์ž…๋‹ˆ๋‹ค. 

 

 

caption

username, caption ์ด ํ‘œ์‹œ๋œ ํ™”๋ฉด์ž…๋‹ˆ๋‹ค. 

 

truncate  ๊ธ€์ž๊ฐ€ ํ™”๋ฉด์— ๋„˜์น˜๋ฉด ๋„˜์น˜๋Š” ๋ถ€๋ถ„์€ ์ž๋ฅด๊ณ  … ์ค„์ž„๋ง๋กœ ํ‘œ์‹œ

 

 

input box

input box๋Š” border๋ฅผ

 ์—†์• ๊ณ  ๊น”๋”ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜์˜€์Šต๋‹ˆ๋‹ค. 

border-none focus:ring-0 outline-none 

 

MinProfile

Suggestion

๋Œ“๊ธ€