반응형
반응형
  • 파일
    • ad-item
  • 디렉토리
    • ad-item
  • 변수명
    • camelCase → rightBorderCell
  • model
    • ad-item.model
  • module
    • ad-item.module
  • resolver
    • ad-item.resolver
  • service
    • ad-item.service
  • dto
    • ad-item.input
  • controller
    • ad-item.controller
  • class명
    • AdItemController, AdItemResolver, AdItemInput, AdItemModule,AdItemService
  • enum
반응형
반응형

 

📝──────────────── React Naming ────────────────

  • custom hook file
    • kebab-case
    • 예) use-single-upload
  • custom hook
    • use + PascalCase
    • 예) useSingleUpload
  • 변수명
    • Camel Case
    • 예) fileFormData
  • 페이지 컴포넌트
    • PascalCase + Page
    • UploadPage
  • 디렉토리
    • kebab-case
    • item-category
  • 파일명
    • kebab-case
    • global-error.tsx
  • Redux Slice
    • 필드명은 스네이크 기법 → ad_item

 

나만의 주석 예시

/** ──── 상태관리 함수 ────**/

/** ──── Query & Mutation ────**/

/** ──── 상태관리 ────**/

/** ──── 이미 등록된 키워드인지 체크 ──── **/ (필요한 경우)

/** ──── TSX ────**/

{/*──── 선택 키워드 ────*/}

등...

 

Code-Map

export enum Code {
  /** ─── 상태 ─── **/
  WEAK = 'WEAK',     // '약함',
  NORMAL = 'NORMAL', // '보통', 
  STRONG = 'STRONG', // '강함,
}

type CodeMap = {
  [key in Code]: string;
};
export const CODE_MAP: CodeMap = {
  [Code.WEAK]: '약함',
  [Code.NORMAL]: '보통',
  [Code.STRONG]: '강함',
}

백엔드에서 코드로 내려온 경우 한글로 변환시키는 Mapping을 만들어두고 한 곳에서 관리하면 좋다

 

 

📝──────────────── Next.js Project Structure ────────────────

프로젝트 구조 (Next + 개인화) [redux + graphql]

src
├ components
│  ├ common
│     └ button.tsx
│  └ dashboard
│       └ pie-chart.tsx
├ config
├ constant
│  └ date-time-constant.ts
├ enums
│  └ code-map.enum.ts
├ assets
│  └ dog.png
├ gql
│  └ dashboard
│       ├ mutations.graphql
│       └ query.graphql
├ hooks
│  └ use-intersection-observer.ts
├ libs
│  └ utils.ts
└ redux
     ├ slices
     ├    └ modal-slice.ts
     ├ reducer.ts
     └ store.ts

 

📝──────────────── React.js Project Structure ────────────────

 

React 프로젝트 구조 (개인화) [redux 사용]

src
├ components
│  ├ common
│  │    └ button.tsx
│  └ dashboard
│       └ pie-chart.tsx
├ config
├ constant
│  └ date-time-constant.ts
├ enums
│  └ code-map.enum.ts
├ assets
│  └ dashboard
│       └ dog.png
├ config
├ services
│  └ dashboard
├ styles
│  └ dashboard
│       └ table.css
├ hooks
│  └ use-intersection-observer.ts
├ utils
│  └ utils.ts
└ redux
     ├ slices
     ├    └ modal-slice.ts
     ├ reducer.ts
     └ store.ts
  • components
    • 재사용 가능한 컴포넌트들이 위치하는 폴더로 컴포넌트는 매우 많아질 수 있기 때문에 이 폴더 내부에서 하위폴더로 추가로 분류합니다
  • assets
    • 컴포넌트 내부에서 사용하는 이미지 파일인 경우 이 assets 폴더에 위치시킵니다
  • hooks
    • 커스텀훅이 위치하는 폴더입니다
  • pages
    • 라우팅을 적용할 때 페이지 컴포넌트를 이 폴더에 위치시킵니다
  • enums
    • enum 관련 파일이 들어갑니다
  • constants
    • 공통적으로 사용되는 상수들을 정의한 파일들이 위치하는 폴더입니다
  • config
    • config 파일이 따로 있는 경우 둡니다
  • styles
    • css 파일이 포함되는 폴더입니다
  • service
    • 외부 API와 상호작용하는 모든 코드들을 넣으며 auth와 같이 인증과 관련된 파일을 포함합니다
  • utils
    • 정규표현식 패턴이나 공통함수 등 공통으로 사용하는 유틸 파일들이 위치하는 폴더
  • redux
    • 리덕스 폴더가 들어갑니다

 

 

프로젝트 구조의 정답은 없다 React의 경우 feature으로 분리하는 방식을 많이 이야기한다 이걸 사용하면 ToDo 기능, 인증 등으로 기능별로 분리할 수 있다 feature/ToDo안에 ToDo관련 components, ToDo.tsx, css 등 관련된 걸 다 넣는다 이럴 경우 기능별로 딱 깔끔하게 되겠지만 양이 엄청 많아질 거고 A라는 페이지에서 쓰는 ToDo랑 B라는 페이지에서 쓰는 ToDo가 살짝 다를 수도 있다 특징별로 분리한 건 어떤 기능이 있는지 확인하고 기능별로 분리가 철저해 보이지만 실제 프로젝트를 하면 재활용성을 완벽하게 하는 경우가 그렇게 많지 않다

 

그럴바에 그냥 DDD구조처럼 중복되더라도 (Auth같이 무조건 공통인 경우는 따로 분리 필요) 살짝 달라서 안에서 분기태우고 이럴바에야 그냥 또 다른 이름으로 제대로 만드는게 좋아보인다

또한 업무도 페이지의 어떤 영역을 맡게 될테고 백엔드도 DDD구조로 가면 프론트에서도 찾기 쉽게 DDD로 가져가는게 프로젝트 이해와 해당 페이지를 찾는게 더 쉬워보인다 그래서 개인적으로는 DDD의 구조를 가져가는게 좋아보인다 

→ 물론 프론트랑 백엔드랑 확실히 구분되어있어서 프론트는 백엔드 프로젝트를 볼 일이 없을 경우는 제

 

물론 회사 프로젝트에서 이미 진행되고 있는 구조가 있다면 그렇게 따라가는 게 맞다

만약 이런 룰을 명확하게 하고 싶다면 프레임워크를 이용하면 된다 (Next.js)

 

 

 

 

🔗 참고 및 출처

https://velog.io/@cjkangme/%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%9D%98-%ED%8F%B4%EB%8D%94-%EA%B5%AC%EC%A1%B0

https://velog.io/@sisofiy626/React-%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%9D%98-%ED%8F%B4%EB%8D%94-%EA%B5%AC%EC%A1%B0

반응형
반응형

 

https://www.npmjs.com/package/graphql-upload

 

graphql-upload

Middleware and an Upload scalar to add support for GraphQL multipart requests (file uploads via queries and mutations) to various Node.js GraphQL servers.. Latest version: 16.0.2, last published: a year ago. Start using graphql-upload in your project by ru

www.npmjs.com

"graphql-upload": "^13.0.0"
"@types/graphql-upload": "^8.0.12"

13버전으로 패키지 설치 → 14버전 이상일 경우 패키지가 .mjs로 만들어져있어서 ts.config설정을 따로 하지 않는 이상 경로 에러 발생

 

app.use(graphqlUploadExpress());

 

Nest.js에 해당 설정 추가해야 파일 업로드 (Multipart/form-data 및 파일 스트림 전송 가능)

 

import { InputType, Field } from '@nestjs/graphql';
import { FileUpload, GraphQLUpload } from 'graphql-upload';

@InputType()
export class FileInput {
  @Field(() => String, { description: '첨부 파일명' })
  file_name: string;

  @Field(() => String, { description: '첨부 파일 타입' })
  file_content_type: string;

  @Field(() => GraphQLUpload, { description: '첨부 파일 blob' })
  file_data: Promise<FileUpload>;
}

GraphQL Mapping Input

 

const data = await fileInput.file_data;
const mimetype = data.mimetype;
const fileName = data.filename;
const fileStream = data.createReadStream();

const chunks = [];
for await (const chunk of fileStream) {
  chunks.push(chunk);
}
const fileBuffer = Buffer.concat(chunks);

비즈니스 로직

 

const [formData, setFormData] = useState(new FormData());

const changeFile = (event: React.ChangeEvent<HTMLInputElement>) => {
    if (event.target.files && event.target.files.length > 0) {
      const fileFormData = new FormData();
      const file = event.target.files?.[0];

      /** GraphQL 쿼리와 변수를 formData에 추가 **/
      fileFormData.append(
        'operations',
        JSON.stringify({
          query: `
            mutation fileUpload($fileInput: FileInput!) {
              fileUpload(fileInput: $fileInput)
            }
          `,
          variables: {
            fileInput: {
              file_name: file.name,
              file_content_type: file.type,
            },
          },
        }),
      );

      /** 이진 데이터 매핑 **/
      fileFormData.append(
        'map',
        JSON.stringify({ '0': ['variables.fileInput.file_data'] }),
      );

      /** 이진 데이터 매핑 **/
      fileFormData.append('0', file);
      setFormData(fileFormData);
    }
  };

  const upload = useCallback(() => {
    fetch('http://localhost:4000/graphql', {
      method: 'POST',
      headers: {
        'x-apollo-operation-name': 'UploadFile', // application/json외 CSRF 방지 허용 Header
      },
      body: formData,
    })
      .then((response) => response.json())
      .then((data) => console.log(data))
      .catch((error) => console.error(error));
  }, [formData]);
  
<input type="file" onChange={changeFile} />
<button onClick={() => upload()}>Upload</button>

클라이언트에서 호출할 때 mutlipart/form-data 형식으로 보내야한다

이진 데이터의 경우 따로 매핑이 필요하다 → GraphQL의 경우 applicaiton/json으로만 통신하게 되어있기 때문에 나머지는 막아놨다 허용하게 하려면 Header에 값을 붙여서 보내야 mutlipart/form-data도 허용이 된다

 

자세한 내용들은 아래 참고 및 출처를 확인 하길 바랍니다

 

!!!

근데 해보니까 이진데이터를 보내서 받아 DB에 저장까지는 되었다만 DB 이진 데이터를 다시 GraphQL로 return이 안 된다 GraphQL은 application/json 타입으로만 return하는 형태로 만들어져있기 때문에 Response 객체를 얻어서 조작하는 행위가 안 됨 → Response에 Buffer를 담고 어떤 파일 형식인지 Header에 보내면 호출 시 해당 파일 보내질텐데 그게 안 됨 이럴바에 그냥 base64로 보내고 base64로 받자

 

🔗 참고 및 출처

https://medium.com/prisma-korea/graphql-%ED%8C%8C%EC%9D%BC-%EC%A0%84%EC%86%A1-b41c5f4deca0

https://graphql-compose.github.io/docs/guide/file-uploads.html

https://www.apollographql.com/docs/apollo-server/v3/data/file-uploads/

 

반응형
반응형

📝스트림

스트림은 데이터를 바이트 단위로 연속적으로 전달하는 일련의 데이터입니다

 

📝버퍼 / 버퍼링

스트림 데이터를 조금씩 저장하며 처리하고 비우기를 반복하는 메모리 공간으로 이러한 행위를 버퍼링이라고합니다

 

📝스트림 + 버퍼

동영상의 경우 전부 다운 받은 후에 영상을 재생시킨다면 엄청나게 오래 기다려야할 수도 있다 이럴 경우 스트림을 이용하며 버퍼에 처리된 걸 담은 후에 사용자에게 보내주면 사용자는 모든 영상이 로드될 때까지 안 기다려도 볼 수 있다

 

📝Blob (Binary Large Object)

이진 데이터를 저장하기 위한 데이터 형식으로 주로 이미지, 오디오, 비디오, 문서 등의 바이너리 데이터를 저장하는 사용됩니다

멀티미디어 파일들은 대다수 용량이 큰 경우가 많기 때문에, 이를 데이터베이스에 효과적으로 저장하기 위해 고안된 자료형으로 바이너리 데이터를 다루기 위해 객체(Object) 저장한다

 

📝ArrayBuffer

객체는 이미지, 동영상과 같은 멀티미디어 데이터 덩어리를 표준 자바스크립트(브라우저)에서 다루기 위해 도입됐다 즉, 자바스크립트에서 원시 데이터(바이너리 데이터)를 직접 다루는 수단이다

이러한 이진데이터를 직접 보거나 수정하기 위해서는 위해서는 TypedArray가 따로 필요하다

 

 

TypedArray에는 여러개가 있다

  • Uint8Array
    • 0 ~ 255 범위를 다룬다
  • Uint16Array
    • 0 ~ 65535 범위를 다룬다
  • Uint32Array
    • 0 ~ 4294967295 범위를 다룬다
  • Float64Array
    • 8바이트 단위로 부동 소수점 방식으로 접근 가능한 view 객체

 

let buffer = new ArrayBuffer(16);
let view = new Uint8Array(buffer);

view[0] = 255;

console.log(buffer);
console.log(view);

ArrayBuffer에 데이터 넣어서 확인해볼 수 있다 Unit8Array이기 때문에 255까지 표현이 가능하기 때문에 256이 들어오면 0이 된다

 

콘솔창에 메모리 아이콘을 누르면 바이너리를 확인할 수 있다 FF 즉 255가 들어간 걸 확인 가능

 

 

📝 ArrayBuffer로 데이터 보내기

<input type="file" id="fileInput">

document.getElementById('fileInput').addEventListener('change', async (event) => {
  const file = event.target.files[0];
  const reader = new FileReader();

  reader.onloadend = async () => {
    const arrayBuffer = reader.result;

    // fetch API를 사용하여 서버로 데이터 전송
    const response = await fetch('your-backend-endpoint-url', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/octet-stream',
      },
      body: arrayBuffer,
    });

    // 서버로부터의 응답 처리
    const result = await response.text();
    console.log(result);
  };

  // File To ArrayBuffer
  reader.readAsArrayBuffer(file);
});

<input type="file">에서 등록된 파일의 정보를 읽어 ArrayBuffer로 바꾼 뒤 octet-stream이라는 이진형태로 보내겠다는 Content-Type을 설정하고 보내면 된다

문제사항은 얘가 이미지인지 텍스트인지 동영상인지 등에 대한 정보가 없기 때문에 Base64로 인코딩 및 MIME-TYPE을 Json에 담아서 보낸다 이럴 경우 ArrayBuffer로 못 보낸다 근데 굳이 ArrayBuffer로 어렵게 보내는 것보다는 아래에 설명하는 방법으로 하는게 더 적합하다

 

 

📝File로 파일 업로드

document.getElementById('uploadButton').addEventListener('click', function() {
  const fileInput = document.getElementById('fileInput');
  if (fileInput.files.length > 0) {
    const file = fileInput.files[0];
    const formData = new FormData();
    formData.append('file', file);

    fetch('your-backend-endpoint-url', {
      method: 'POST',
      body: formData
    }).then(response => {
      return response.text();
    }).then(data => {
      console.log(data);
    }).catch(error => {
      console.error(error);
    });
  }
});

<input type="file" id="fileInput">

<input type="file">의 경우 파일 업로드할 때 사용되는데 File 객체를 가진다 이건 Blob을 상속받아 만들어진 것이다

formData객체를 만들어서 보낼 경우 Content-Type 설정 안 해도 알아서 설정된다

 

📝FormData vs Json

FormData랑 Json이랑 키-값으로 이루어진 건 비슷하다 하지만 Json은 텍스트, 숫자, 배열, 객체 등 다양한 데이터 타입을 지원하지만, 이진 데이터(예: 파일)를 직접적으로 다루지는 않습니다 그에 반해 FormData는 이진데이터도 취급을 한다

 

 

🔗 참고 및 출처

https://curryyou.tistory.com/441

https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-Base64-Blob-ArrayBuffer-File-%EB%8B%A4%EB%A3%A8%EA%B8%B0-%EC%A0%95%EB%A7%90-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-%EC%89%BD%EA%B2%8C-%EC%84%A4%EB%AA%85#arraybuffer

반응형
반응형

📝주식선물

물건을 먼저 사는 것에서 유래했으며 예를 들면 A라는 떡가게가 있고 B라는 농부가 있다 A는 이번년도말에 쌀 가격이 오를 거라 생각해 B에게 연말에 15,000원에 쌀을 받기로 계약을 한다
쌀가격이 오른 경우 떡가게는 이득을 보고 쌀가격이 내린 경우에는 떡가게는 손해를 보게 된다

대부분 선물옵션이라고 하는 것은 지수선물을 의미한다
국장에서 코스피200 선물매수라는 건 코스피200 지수가 상승한다에 배팅하는 것을 의미하고 코스피200 선물매도지수가 하락한다에 배팅하는 것을 의미한다

📝상품선물

원유, 금, 은, 구리 등에 투자하는 선물거래 상품
 

📝금융선물

지수, 채권, 달러 등에 투자하는 선물거래 상품

 

📝신용거래

물건을 먼저 받고 돈을 나중에 주게 되는 거래

 

📝시장가격

수요와 공급에 따라 결정 수요가 많을수록 비싸지고 수요가 없을수록 싸진다
희귀하고 가치있을 수록 비싸지고 (메시 유니폼) 가치가 없고 흔할 수록(내가 입은 유니폼) 싸진다

 

📝IPO

기업공개로서 상장해도 될지를 심사하고상장된 주식이 거래될 수 있도록 기업의 주요정보를 공개하는 절차이다.

 

📝M&A

기업합병과 경영권 인수를 뜻하는 기업인수를 의미한다.

 

📝시리즈A

최초 투자금을 받는 단계로서 약 100억의 기업 가치를 의미합니다.


📝시리즈B

제품이 상품화 되는 단계로서 기업 가치는 약 400억의 기업 가치를 의미합니다.


📝시리즈C

상품화 된 제품을 여러 시장의 확장하는 수준으로서 단기간에 크게 키울 수 있는 방법으로는 M&A가 있습니다.
시리즈C정도면 이미 국내에서 크게 성공을 거뒀고 해외로 확장할 시기입니다.

 

참고로 간단하게 정리한 내용이고 각 단계에 대한 투자자의 생각, 창업자의 생각 등에 대한 요소는 들어가있지 않습니다.

 

 

🔗 참고 및 출처

https://www.youtube.com/watch?v=7OulYY8Pffs

반응형
반응형

📝데이터 네트워크 흐름

 

✅ OS, WAS, DB가 같은 서버에 설치되어있는 경우 (파일 읽기도 동일)

 

클라이언트 요청 → NIC Buffer → Socket Buffer → Application Buffer → 클라이언트 요청 정보 해석 (파일 읽기일 때) → OS에 파일읽기 요청 → Read Buffer 저장 → Application Buffer로 전송 → Socket Buffer로 전송 → NIC Buffer로 전송 → 네트워크를 이용해 클라이언트에게 전송

 

  • Read Buffer
    • 파일 시스템이나 데이터베이스로부터 데이터를 읽을 때 사용됩니다
  • Application Buffer
    • 보통 사용자가 HTTP로 데이터를 요청하기 때문에 WAS에 Read Buffer 내용이 들어가게 됩니다
    • 필요한 형식으로 변환하거나 추가 데이터 작업을 할 수 있습니다
  • Socket Buffer
    • 네트워크에 전송되기 전에 임시적으로 저장됩니다
    • TCP/IP 프로토콜을 통해 데이터를 전송하기 전에 이곳에 저장되게 됩니다
  • NIC Buffer
    • 데이터가 네트워크를 통해 전송되기 위해서는 먼저 NIC Buffer로 이동하게 됩니다
  • DMA copy
    • DMA(Direct Memory Access Copy)로 CPU의 개입 없이 메모리간 데이터를 직접 선송할 수 있는 컴퓨터 시스템 기능입니다
    • OS 커널 영역에서는 DMA Copy를 한다
  • CPU Copy
    • CPU를 이용해 데이터를 메모리에서 다른 메모리로 이동하는 방법으로 WAS로 읽어들이는 경우 사용된다

 

✅ OS, WAS, DB가 다른 서버에 설치되어있는 경우

클라이언트 요청 → NIC Buffer로 전송 → Socket Buffer로 전송 → Application Buffer로 전송 → DB 호출 → Socket Buffer에 DB 요청 정보 보내기 → NIC Buffer로 전송 → DB 설치 서버에 NIC Buffer로 접근 → Socket Buffer로 전송 → DB에서 처리 후 Socket Buffer로 전송 → NIC Buffer로 전송 → NIC Buffer에서 받기 → Socket Buffer로 전송 → WAS 추가 작업 진행  끝낸 후 Socket Buffer로 전송 → NIC Buffer로 전송 → 네트워크를 이용해 클라이언트에게 전송

 

✅ OS, WAS 같은 서버에 설치되어있는 경우 (DB Connection X)

클라이언트 요청 → NIC buffer → Application Buffer로 전송 → Socket Buffer로 전송 → NIC Buffer로 전송 → 네트워크를 이용해 클라이언트에게 전송

 

📝Zero Copy

맨 위에 설명한 OS, WAS, DB가 같은 서버에 있는 경우 WAS에서 따로 처리가 필요 없는 경우 Read Buffer를 바로 Socket Buffer를 이용하는게 좋다

 

굳이 Socket에 담을 필요 없이 NIC Buffer로 바로 보내면 더 성능에 이점을 가지게 된다 이럴 경우 Buffer에 담는 행위가 줄어들기 때문에 Copy의 과정이 Zero에 가까워진다

 

 

반응형
반응형

백엔드는 프리즈마 ORM을 사용했고 커서기반으로 구현했습니다

 

Page

const ItemPage = () => {
  const TAKE = 10;
  const MEMBER_ID = 1; // 1 ~ 5
  const FIRST_CURSOR = 1;

  const { data, fetchNextPage, hasNextPage, isFetching } =
    useInfiniteFindItemsQuery(
      {
        memberId: MEMBER_ID,
        lastCursorId: FIRST_CURSOR,
      },
      {
        getNextPageParam: (lastPage, allPages) => {
          if (lastPage.findItems.length === 0) {
            return undefined;
          } else {
            return {
              memberId: MEMBER_ID,
              lastCursorId:
                lastPage.findItems[lastPage.findItems.length - 1]?.id + 1,
            };
          }
        },
        enabled: false,
        initialPageParam: {
          memberId: MEMBER_ID,
          lastCursorId: FIRST_CURSOR,
        },
      },
    );

  useEffect(() => {
    fetchNextPage();
  }, []);

  const observerRef = useIntersectionObserver({
    isFetching,
    hasNextPage,
    fetchNextPage,
  });

  return (
    <main className="mx-24 my-16">
      <SearchBar />
      {/*<Suspense fallback={ItemPage.Skeleton()}>*/}
      <div className="grid grid-cols-5 gap-x-14">
        {data?.pages.map((page, pageNum) =>
          page.findItems.map((item, index) => (
            <Item index={index + pageNum * TAKE} key={item.id} item={item} />
          )),
        )}
      </div>
      {/*</Suspense>*/}
      <div ref={observerRef} className="h-1" />
    </main>
  );
};

export default ItemPage;

 

 

최하단 엘리먼트 요소 감지 훅 및 다음 페이지 데이터 Fetch

import React, { useCallback, useEffect, useRef, useState } from 'react';
import {
  FetchNextPageOptions,
  InfiniteData,
  InfiniteQueryObserverResult,
} from '@tanstack/react-query';
import { IFindItemsQuery } from '@/gql/generated/graphql';

//hook props interface
interface IuseIntersectionObserverProps<TData = unknown> {
  isFetching: boolean | undefined;
  hasNextPage: boolean | undefined;
  fetchNextPage: (
    options?: FetchNextPageOptions,
  ) => Promise<
    InfiniteQueryObserverResult<InfiniteData<TData, unknown>, unknown>
  >;
}

export const useIntersectionObserver = <TData>({
  isFetching,
  hasNextPage,
  fetchNextPage,
}: IuseIntersectionObserverProps<TData>) => {
  const options = {
    root: null, // 감지할 대상 지정 null일 경우 뷰포트가 대상
    rootMargin: '0px', // root요소가 가장자리에 도달하는 즉시 교차 이벤트 감지
    threshold: 1.0, // 얼마나 많이 root요소와 교차하는지 설정 → 1.0 = 100% 완전히 겹친다
  };

  const handleIntersection = useCallback(
    (entries: IntersectionObserverEntry[]) => {
      const target = entries[0];
      if (target.isIntersecting && hasNextPage && !isFetching) {
        fetchNextPage();
      }
    },
    [hasNextPage, isFetching, fetchNextPage],
  );

  const observerRef = useRef(null);

  useEffect(() => {
    const observer = new IntersectionObserver(handleIntersection, options);
    if (observerRef.current) {
      observer.observe(observerRef.current);
    }
    return () => {
      observer.disconnect();
    };
  }, [handleIntersection, options]);

  return observerRef;
};

 

반응형
반응형

📝데이터 싸이언스 

데이터를 수집, 분석하고 이를 통해 의사 결정을 내리는 과정에 참여통계, 머신 러닝, 데이터 시각화 등 다양한 기술과 도구를 사용하여 데이터를 이해하고 활용

 

📝풀스택 개발자

프론트엔드 및 백엔드 개발을 모두 다룰 수 있는 개발자를 의미하며 우리가 지향해야 한다

 

📝 DevOps (Development and Operations)

소프트웨어 개발과 운영을 통합하여 효율적이고 신속한 개발 및 배포 프로세스를 구축하는 역할을 수행 → 자동화, 지속적 통합 및 지속적 배포 (CI/CD), 인프라스트럭처 관리 등에 집중

 

📝임베디드 엔지니어

하드웨어와 소프트웨어를 결합하여 특정 장치나 시스템에 포함된 소프트웨어를 개발 IoT 디바이스, 드론 등

 

📝알고리즘 엔지니어

복잡한 문제를 해결하기 위해 효율적이고 최적화된 알고리즘을 설계 및 구현

 

📝OS / LINUX 개발자

운영 체제 또는 리눅스 시스템의 커널, 드라이버 및 핵심 구성 요소를 개발

 

📝코딩언어 / Compiler 개발자

새로운 프로그래밍 언어를 설계하거나 기존 언어의 컴파일러를 개발파이썬, C++ 개발 등

반응형
반응형

📝분석 (모니터링)

/** _components/web-vitals.tsx **/
'use client'
import { useReportWebVitals } from 'next/web-vitals'

export function WebVitals() {
    useReportWebVitals((metric) => {
        console.log(metric)
    })
}

/** /app/layout.tsx **/
import { WebVitals } from './_components/web-vitals'
 
export default function Layout({ children }) {
  return (
    <html>
      <body>
        <WebVitals />
        {children}
      </body>
    </html>
  )
}

Next.js에는 성능 지표 측정 및 보고 기능이 내장되어 있습니다

 

  1. Time to First Byte (TTFB)
    • 웹 페이지 요청 후 서버가 첫 번째 바이트를 브라우저에게 전송하는 데 걸리는 시간입니다 클라이언트가 서버로부터 첫 데이터를 받기까지의 대기 시간을 측정하여 네트워크 및 서버 성능을 평가하는 데 사용됩니다.
  2. First Contentful Paint (FCP)
    • 사용자가 웹 페이지를 열고 첫 번째 콘텐츠가 화면에 나타나기 시작하는 시점입니다. 일반적으로 텍스트나 이미지와 같은 가장 먼저 렌더링되는 콘텐츠를 말합니다.
  3. Largest Contentful Paint (LCP)
    • 웹 페이지에서 가장 큰 콘텐츠 요소가 화면에 나타나는 데 걸리는 시간입니다. 주로 이미지나 텍스트 블록과 같은 큰 콘텐츠를 측정하여 사용자 경험을 평가하는 데 사용됩니다.
  4. First Input Delay (FID)
    • 사용자가 페이지와 상호 작용(클릭, 터치 등)하고 브라우저가 실제로 이벤트에 응답하기까지 걸리는 시간입니다. 주로 사용자와의 상호 작용에 대한 웹 페이지의 반응성을 측정하는 데 사용됩니다.
  5. Cumulative Layout Shift (CLS)
    • 페이지가 로드되는 동안 레이아웃이 얼마나 불안정한지를 측정하는 지표입니다. 주로 이미지나 광고와 같은 동적으로 로드되는 콘텐츠가 페이지의 레이아웃을 어떻게 변경하는지를 평가하여 사용자가 의도치 않게 클릭하거나 다른 동작을 할 때의 불편함을 측정합니다
  6. Interaction to Next Paint (INP)
    • 사용자 상호 작용 후 다음 렌더링이 시작되기까지의 시간을 나타냅니다. 사용자가 웹 페이지와 상호 작용한 후 얼마나 빨리 새로운 콘텐츠나 업데이트가 화면에 나타나는지를 측정하여 사용자 경험을 평가합니다.

 

결과를 모든 엔드포인트로 보내 사이트의 실제 사용자 성과를 측정하고 추적할 수 있습니다

 

 

📝Typescript

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    typedRoutes: true,
  },
}
 
module.exports = nextConfig

next/link에서 오타 방지 기능 추가 가능

 

📝ESLint

Next.js는 ESLint 플러그인(@next/eslint-plugin-next)을 제공하는데 기본적으로 이러한 기능들이 적용 되어있다

 

📝환경 변수

/** .env **/
DB_HOST=localhost
DB_USER=myuser
DB_PASS=mypassword

TWITTER_USER=nextjs
TWITTER_URL=https://twitter.com/$TWITTER_USER

/** API.tsx **/
export async function GET() {
  const db = await myDB.connect({
    host: process.env.DB_HOST,
    username: process.env.DB_USER,
    password: process.env.DB_PASS,
  })
  // ...
}
  • Next.js에는 .env.local에서 process.env로 환경 변수를 로드하는 기능이 내장되어 있습니다
  • env.local을 통해 로드 된 모든 환경 변수는 Node.js에서만 사용할 수 있는데 브라우저에 변수를 사용하려면 변수 앞에 NEXT_PUBLIC_을 붙여야합니다
  • .env.test 파일에서도 동일한 작업을 수행할 수 있습니다 테스트 목적으로만 특정 환경 변수를 설정해야 하는 jest 또는 cypress와 같은 도구를 사용하여 테스트를 실행할 때 유용합니다

 

📝상대경로 , 절대경로, 경로 별칭

상대경로, 절대경로, 경로 별칭도 사용이 가능하다

 

📝MDX

마크다운을 처리할 수 있는 노드 패키지도 지원한다

반응형