inflearn logo
강의

강의

N
챌린지

챌린지

멘토링

멘토링

N
클립

클립

로드맵

로드맵

지식공유

[인프런 워밍업 클럽 3기] 풀스택 과정 1주차 발자국

Jii
0

1주차 학습 범위

 

📓 학습 기록

Supabase

Vendor Lock-in

특정 클라우드 서비스나 플랫폼을 사용하기 시작하면, 다른 서비스로 쉽게 이동할 수 없게 되는 상황

 

Next.js

export async function GET(request: Request) {}
export async function HEAD(request: Request) {}
export async function POST(request: Request) {}
export async function PUT(request: Request) {}
export async function DELETE(request: Request) {}
export async function PATCH(request: Request) {}

 

<Link> 사용 권장

import Link from 'next/link'

export default function Page() {
	return <Link href="/dashboard">Dashboard</Link>
}

 

 Server Actions

// pages/api/form.ts (또는 app/api/form/route.ts)
import { NextResponse } from "next/server";

export async function POST(req: Request) {
  const data = await req.json();
  console.log("데이터 수신:", data);

  return NextResponse.json({ message: "성공!", data });
}
 // 클라이언트에서 요청
async function submitForm(formData) {
  const res = await fetch("/api/form", {
    method: "POST",
    body: JSON.stringify(formData),
    headers: { "Content-Type": "application/json" },
  });
  const result = await res.json();
  console.log(result);
}
// 서버 액션 정의
"use server";

export async function submitForm(formData: FormData) {
  console.log("서버에서 데이터 처리:", formData);
  return { message: "성공!" };
}
// 클라이언트에서 바로 호출
"use client";
import { useState } from "react";
import { submitForm } from "./actions"; // 서버 함수 불러오기

export default function FormComponent() {
  const [message, setMessage] = useState("");

  async function handleSubmit(event) {
    event.preventDefault();
    const formData = new FormData(event.target);
    
    const result = await submitForm(formData); // API 요청 없이 직접 호출
    setMessage(result.message);
  }

  return (
    <form onSubmit={handleSubmit}>
      <input name="name" placeholder="이름 입력" />
      <button type="submit">제출</button>
      <p>{message}</p>
    </form>
  );
}

Summary

외부 API와 통신해야 할 경우 → fetch를 써야 하므로 기존 API Route 방식 사용

폼 제출, 데이터 처리 같은 간단한 서버 로직 → Server Actions 사용

 

 Metadata

export const metadata = {
  title: "내 멋진 웹사이트 🚀",
  description: "Next.js를 활용한 SEO 최적화 페이지",
};
// 동적 Metadata (게시글 제목 반영)
import { Metadata } from "next";

export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await fetch(`https://api.example.com/posts/${params.id}`).then((res) => res.json());

  return {
    title: post.title,
    description: post.summary,
  };
}

 

TailwindCSS

유틸리티 퍼스트(Utility-First)

미리 정의된 작은 스타일 단위(유틸리티 클래스)를 조합하여 스타일을 적용하는 방식

 

Recoil

 

atom

import { atom } from 'recoil';

// 'countState'라는 이름을 가진 atom을 생성
export const countState = atom({
  key: 'countState',  // 각 atom은 고유한 'key' 값을 가져야 함
  default: 0,  // atom의 초기값
});
// 첫 번째 컴포넌트: 카운트 증가
const Incrementer = () => {
  const [count, setCount] = useRecoilState(countState);
  return <button onClick={() => setCount(count + 1)}>카운트 증가</button>;
};

// 두 번째 컴포넌트: 카운트 표시
const DisplayCounter = () => {
  const count = useRecoilValue(countState);
  return <p>현재 카운트: {count}</p>;
};

// Incrementer와 DisplayCounter 컴포넌트는 같은 atom (countState)을 공유하고 있는 것!

 

selector

// atom 생성
import { atom } from 'recoil';

export const nameState = atom({
  key: 'nameState',
  default: '홍길동',
});

export const birthYearState = atom({
  key: 'birthYearState',
  default: 1990,
});
// selector 생성: birthYearState 값을 바탕으로 사용자의 나이를 계산 (ageSelector)
import { selector } from 'recoil';
import { birthYearState } from './atoms';

export const ageSelector = selector({
  key: 'ageSelector',
  get: ({ get }) => {
    const birthYear = get(birthYearState);  // atom 값 가져오기
    const currentYear = new Date().getFullYear();
    return currentYear - birthYear;
  },
});

 

React-Query (TanStack)

 

useQuery

import { useQuery } from '@tanstack/react-query';

const fetchTodos = async () => {
  const res = await fetch('/api/todos');
  return res.json();
};

const TodoList = () => {
  const { data, isLoading, error } = useQuery({ queryKey: ['todos'], queryFn: fetchTodos });

  if (isLoading) return <p>Loading...</p>;
  if (error) return <p>Error loading todos</p>;

  return (
    <ul>
      {data.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  );
};

 

useMutation

import { useMutation, useQueryClient } from '@tanstack/react-query';

const addTodo = async (newTodo) => {
  const res = await fetch('/api/todos', {
    method: 'POST',
    body: JSON.stringify(newTodo),
    headers: { 'Content-Type': 'application/json' },
  });
  return res.json();
};

const AddTodo = () => {
  const queryClient = useQueryClient();
  const mutation = useMutation({
    mutationFn: addTodo,
    onSuccess: () => {
      queryClient.invalidateQueries(['todos']); // todos 목록을 다시 불러옴
    },
  });

  return (
    <button onClick={() => mutation.mutate({ title: '새로운 할 일' })}>
      할 일 추가
    </button>
  );
};

 

 

📎 미션

image

 

📝회고

Next.js와 TailwindCSS, Firebase는 기존에 사용해 봤었고 Supabase, Recoil, React Query는 이번에 처음 배웠다.

기존에 사용해보지 않았던 기술을 사용해보고 싶었는데, 이 스터디에서는 사용해본 기술과 처음 배우는 기술이 적당히 섞여있어서 좋았다.

 

풀스택 풀스택 워밍업클럽 발자국

답변 0