Next.js

  “use client”

=> 브라우저(클라이언트)에서 실행, 해당 코드를 미 적용 시 노드에서 실행(서버)에서 실행

 

노드에서 사용하는 이유 > 서버사이드 랜더링을 하기 위해서 > 구글에서 웹이 노출하기 위해서

 

★ image

이미지 태그를 사용할 시 너비와 높이를 지정해야 하는 이유 : 레이아웃 시프트가 발생한다.

레이아웃 시프트: 웹 페이지가 로드되는 동안 기존에 있던 콘텐츠가 갑자기 위치를 바꾸거나 밀려나는 현상

 

대부분의 이미지는 퍼블릭 폴더에 둔다(경우에 따라서 따로 빼올 수도 있다.)

 

Next.js 공식문서에서 Linking and Navigating

Link를 사용하면 페이지가 새로고침되지 않는다. *프리패치 기능

 

*프리패치(Prefetch) : 링크를 클릭하기 전에 해당 페이지의 리소스를 미리 다운로드

 


DB

 데이터 유형

관리데이터 : 수정이 필요한 데이터

로그데이터 : 삭제와 수정이 필요 없는 데이터 (빅데이터가 이에 해당한다.)

 

★ 데이터를 사용하는 이유

사용자의 행위(결제내역,진료내역,주문내역 등..)들을 기록이나 저장

말과 글을 분석하기 보다는 행동과 조회로 분석한다.

 

★  데이터 모델링 과정

  • 개념적 구조 : 사용자 입장에서 어떤 데이터로 이루어져 있는가?
  • 논리적 구조 : 데이터들을 테이블 형태로 만들며 어떻게 상호관계하는 구조를 만들며 표현할 것인가?
  • 물리적 구조 : 데이터들을 어디에 저장하고 보관할 것인가?

시스템 ?

둘 이상 (각 역할자가 존재) 의 여러 구성 요소들이 서로 연결되어 상호작용하는 집합체(소프트웨어, 하드웨어 등...)

 

E-R 데이터 모델링

E-R 데이터 모델링 기호와 의미

예로 식당을 들었을 때, 주체가 되는 개체(엔티티) 즉 손님, 요리사, 웨이터가 되는 것이고

무언가를 하는 행위 혹은 절차를 관계(릴레이션) 결제,주문,메뉴등록 등이 해당하며,

속성은 관계, 행위 안에 포함하고 있는 걸 말한다. 결제에는 결제일자, 주문한 메뉴에 대한 금액, 결제 수단에 해당한다.

개체와 관계에 따라 1:1, 1:N, N:N이 존재한다. 

 

 

정규화

데이터를 중복없이 간단하고 효율적으로 관리하는 방법

 

제1정규화 : 속성 값이 더 이상 쪼갤 수 없는 원자값(한칸에 한 개의 값)

 

ex) A : 숫자,문자 / B : 문자 (X)
      A : 숫자 / A : 문자 / B : 문자 (O)

 

제2정규화 : 완전 함수 종속,

기본키의 일부에만 종속되는 속성(컬럼)을 제거

한 개의 테이블을 관계에 따라서 두 개 이상으로 분리

 

ex) 적용 전 => 학번, 과목코드, 성적, 담당교수(학교 테이블)

      적용 후 => 학번, 과목코드, 성적 (수강 테이블)
                        과목코드, 담당교수 (과목정보 테이블)

 

제3정규화 : 이행적 종속(A->B, B->C가 성립할 때 A->C가 성립) 제거

기본키가 아닌 속성이 다른 기본키가 아닌 속성을 결정

 

ex) 적용 전 => 학번, 학생이름, 학과명, 학과 사무실 위치(학교 테이블)

      적용 후 => 학번, 학생이름 , 학과명 (학생 테이블)
                        학과명 , 학과 사무실 위치 (학과정보 테이블)

 

 

 

 

회고 : Next.js는 공식문서를 한번 훑어보자 무의식적으로 쓰고 보는 것과

흐름을 알고 난 후에 보는 것과 체감이 많이 다르다. 

먼저 올바른 DB를 만들기 위해서는 시스템이라는 설계도를 마인드맵처럼 생각하고

개체와 관계에 연결 경우를 고려해서 데이터의 테이블을 만들어야 할 필요가 있다.

 

Next.js를 써야하는 이유

리액트는 라이브러리 형태이며 순수 프론트엔드만 가능하지만

Next는 리액트 기반으로 이루어진 프레임 워크이며 풀스택 작업이 가능하다.

 

새 프로젝트 생성 방법

  • 공식 홈페이지 및 링크 참고할 것

 

★ Next.js 폴더구조

  • 코드를 메인에 해당하는 app 디렉터리
  • 코드를 정적으로 보여주는 public 디렉터리
  • 페이지 폴더
    • 폴더 명은 곧 url를 의미한다. but page.tsx가 없으면 url에 해당하지 않는다.
  • 컴포넌트 폴더
    • 바깥에 두는 방법 : 컴포넌트를 한 곳에 모아둠, 다른 앱에서도 적용 시키기 용이함
    • 페이지 안에 두는 방법 : 컴포넌트를 해당 페이지에 적용 되어있다는 것을 알기 쉬움
    • 적용방법 : 컴포넌트를 layout.tsx에 삽입
  • 페이지 명은 소문자 컴포넌트 명은 파스칼로 명시할 것

 

★ Next.js에서 css적용

  • 컴포넌트 폴더 안에 컴포넌트명.module.css로 설정 후 코드 삽입
  • 해당 컴포넌트에 들어가서 경로를 불러오기
    • import "./header.css"; <= 기존에 불러오는 방법
     

1. styles 객체에서 필요한 스타일을 꺼내 쓰는 방식
2. 구조 분해 할당으로 스타일을 변수로 꺼내쓰기

  • style.css = 기본적으로 모든 파일이 공통적으로 받는 스타일시트
  • module.css = 특정 파일에만 적용 시키는 스타일시트

★ 레이아웃 중첩 : 페이지마다 다르게 디자인 해야 하는 경우에 활용

layout.tsx가 2개인 상황 : 부모(app) 레이아웃의 {children} 안에 자식 레이아웃이나 페이지가 들어가는 구조 이미지에는 부모는 RootLLayout에 자식은 admin에 있는 해당하고 admin이 부모면 안에 있는 layout에서 {children}은 사용자가 요청한 페이지

 

 

 

회고 : Next.js를 한번 개인적으로 사용한 적은 있었으나, 무작정 내 것으로 만드는 것보다

수업을 들으면서 과정을 이해하니 프레임워크에 대해 더 쉽게 이해할 수 있었다.

그리고 레이아웃 중첩은 어렵게 느껴지나 익숙해지면 많이 사용하게 될거같다.

 

자세한 코드는 이쪽

더보기
"use client";

import { useState } from "react";
import { TextField, Button, Container, Typography, Box } from "@mui/material";
import Link from "next/link";

export default function Form() {
  const [subject, setSubject] = useState("");
  const [message, setMessage] = useState("");
  const [status, setStatus] = useState("");

  // 기본 이메일 주소
  const email = "ysh940129@gmail.com";

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    const res = await fetch("/api/send-email", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ to: email, subject, message }),
    });

    const data = await res.json();
    setStatus(
      data.success ? "이메일이 성공적으로 전송되었습니다!" : "전송 실패"
    );
  };

  return (
    <Box
      sx={{
        width: "100vw",
        height: "100vh",
        backgroundImage: 'url("/assets/plane.png")',
        backgroundSize: "cover",
        backgroundPosition: "center",
      }}
    >
      <Container
        maxWidth="sm"
        sx={{
          pt: 8,
        }}
      >
        <Container
          sx={{
            backgroundColor: "#ffffff",
            p: 4,
            borderRadius: "20px",
            boxShadow: "0 2px 10px rgba(0, 0, 0, 0.3)",
          }}
        >
          <Typography
            gutterBottom
            sx={{
              display: "flex",
              justifyContent: "center",
              backgroundColor: "#87CEEB",
              height: "50px",
              fontSize: "30px",
              fontWeight: "bold",
              borderRadius: "10px",
              color: "#ffffff",
              boxShadow: "0 2px 10px rgba(0, 0, 0, 0.3)",
              lineHeight: "50px",
            }}
          >
            이메일 보내기
          </Typography>
          <form onSubmit={handleSubmit}>
            <TextField
              placeholder={email}
              fullWidth
              margin="normal"
              disabled // 이메일 입력란을 비활성화
              InputProps={{
                style: {
                  fontWeight: "bold",
                  fontSize: "20px",
                },
              }}
            />
            <TextField
              label="제목"
              fullWidth
              margin="normal"
              value={subject}
              onChange={(e) => setSubject(e.target.value)}
              required
            />
            <TextField
              label="내용"
              fullWidth
              multiline
              rows={6}
              margin="normal"
              value={message}
              onChange={(e) => setMessage(e.target.value)}
              required
            />
            <Container
              disableGutters
              maxWidth="sm"
              sx={{
                display: "flex",
                justifyContent: "space-between",
                marginTop: 2,
              }}
            >
              <Button
                type="submit"
                variant="contained"
                sx={{
                  width: "240px",
                  height: "50px",
                  fontWeight: "bold",
                  backgroundColor: "#87CEEB",
                  fontSize: "20px",
                }}
              >
                보내기
              </Button>
              <Link href={"Contact"}>
                <Button
                  type="button" // 버튼 타입을 'button'으로 변경
                  variant="contained"
                  sx={{
                    width: "240px",
                    height: "50px",
                    fontWeight: "bold",
                    backgroundColor: "#87CEEB",
                    fontSize: "20px",
                  }}
                >
                  뒤로가기
                </Button>
              </Link>
            </Container>
          </form>
          {status && <Typography sx={{ mt: 2 }}>{status}</Typography>}
        </Container>
      </Container>
    </Box>
  );
}

 

자세한 코드는 이쪽

더보기
      <Dialog open={open} onClose={() => setOpen(false)}>
        <DialogTitle
          sx={{
            minHeight: "8vh",
            fontWeight: "bold",
            fontSize: "30px",
            margin: 2,
            lineHeight: "25px",
            display: "block",
            textAlign: "center",
            marginBottom: 5,
            backgroundColor: "#87CEEB",
            color: "#ffffff",
            borderRadius: "10px",
          }}
        >
          {projectName}
        </DialogTitle>
        <DialogContent>
          <Typography
            sx={{
              fontSize: "20px",
              backgroundColor: "#87CEEB",
              color: "#fff",
              height: "40px",
              lineHeight: "40px",
              borderRadius: "10px",
              textAlign: "center",
              marginBottom: 2,
              boxShadow: "0 2px 10px rgba(0, 0, 0, 0.2)",
            }}
          >
            {subTitle1}
          </Typography>
          <CardMedia
            component="img"
            alt="Project"
            image={projectImg}
            sx={{
              width: "100%",
              height: "auto",
              marginTop: 1,
              marginBottom: 8,
              padding: 2,
              borderRadius: "8px",
              boxShadow: "0 2px 10px rgba(0, 0, 0, 0.2)",
            }}
          />
          <Typography
            sx={{
              fontSize: "20px",
              backgroundColor: "#87CEEB",
              color: "#fff",
              height: "40px",
              lineHeight: "40px",
              borderRadius: "10px",
              textAlign: "center",
              marginBottom: 2,
              boxShadow: "0 2px 10px rgba(0, 0, 0, 0.2)",
            }}
          >
            {subTitle2}
          </Typography>
          <CardMedia
            component="img"
            alt="Project"
            image={image1}
            sx={{
              width: "100%",
              height: "auto",
              marginTop: 1,
              marginBottom: 8,
              padding: 2,
              borderRadius: "8px",
              boxShadow: "0 2px 10px rgba(0, 0, 0, 0.2)",
            }}
          />
          <Typography
            sx={{
              fontSize: "20px",
              backgroundColor: "#87CEEB",
              color: "#fff",
              height: "40px",
              lineHeight: "40px",
              borderRadius: "10px",
              textAlign: "center",
              marginBottom: 2,
              boxShadow: "0 2px 10px rgba(0, 0, 0, 0.2)",
            }}
          >
            {subTitle3}
          </Typography>
          <CardMedia
            component="img"
            alt="Project"
            image={image2}
            sx={{
              width: "100%",
              height: "auto",
              marginTop: 1,
              marginBottom: 8,
              padding: 2,
              borderRadius: "8px",
              boxShadow: "0 2px 10px rgba(0, 0, 0, 0.2)",
            }}
          />
          {/* 새탭으로 PPT 파일 보기 */}
          <Link
            href={projectFile || "#"}
            target={projectFile ? "_blank" : ""}
            rel="noopener noreferrer"
            onClick={(e) => {
              if (!projectFile) e.preventDefault(); // 클릭 차단
            }}
          >
            <Button
              sx={{
                height: "40px",
                padding: 2,
                fontSize: "18px",
                backgroundColor: "#87CEEB",
                color: "#fff",
                fontWeight: "bold",
                boxShadow: "0 2px 10px rgba(0, 0, 0, 0.2)",
                opacity: projectFile ? 1 : 0.5, // 비활성화 시 투명도 낮춤
                pointerEvents: projectFile ? "auto" : "none", // 클릭 비활성화
              }}
              disabled={!projectFile} // projectFile이 없으면 버튼 비활성화
            >
              PPT로 자세히 보기
            </Button>
          </Link>
        </DialogContent>
        <DialogActions>
          <Button
            onClick={() => setOpen(false)}
            color="primary"
            sx={{
              height: 40,
              width: 100,
              backgroundColor: "#87CEEB",
              color: "#ffffff",
              fontWeight: "bold",
              fontSize: "20px",
              boxShadow: "0 2px 10px rgba(0, 0, 0, 0.2)",
              margin: 2,
            }}
          >
            닫기
          </Button>
        </DialogActions>
      </Dialog>

 

자세한 코드는 이쪽

https://github.com/YSangH/PortfolioSite/blob/main/src/app/Contact/page.tsx

 

PortfolioSite/src/app/Contact/page.tsx at main · YSangH/PortfolioSite

Contribute to YSangH/PortfolioSite development by creating an account on GitHub.

github.com

 

 

자세한 코드는 이쪽

https://github.com/YSangH/PortfolioSite/blob/main/src/app/Portfolio/page.tsx

 

PortfolioSite/src/app/Portfolio/page.tsx at main · YSangH/PortfolioSite

Contribute to YSangH/PortfolioSite development by creating an account on GitHub.

github.com

 

자세한 코드는 이쪽

https://github.com/YSangH/PortfolioSite/blob/main/src/app/Skills/page.tsx

 

PortfolioSite/src/app/Skills/page.tsx at main · YSangH/PortfolioSite

Contribute to YSangH/PortfolioSite development by creating an account on GitHub.

github.com

 

자세한 코드는 이쪽

https://github.com/YSangH/PortfolioSite/blob/main/src/app/Info/page.tsx

 

PortfolioSite/src/app/Info/page.tsx at main · YSangH/PortfolioSite

Contribute to YSangH/PortfolioSite development by creating an account on GitHub.

github.com

 

+ Recent posts