## 들어가며웹 개발을 하다 보면 CORS(Cross-Origin Resource Sharing) 오류와 마주치게 됩니다. 특히 프론트엔드에서 외부 API를 호출할 때 자주 발생하는 이 문제는 많은 개발자들을 괴롭히곤 합니다. 그런데 Next.js를 사용하면 이런 CORS 문제를 우아하게 해결할 수 있습니다. 이 글에서는 Next.js의 API 라우트가 어떻게 CORS 문제를 해결하고, 웹서버와 WAS(Web Application Server)가 어떻게 동일한 출처에서 공존하는지 알아보겠습니다.## CORS란 무엇인가?CORS는 '교차 출처 리소스 공유'라는 의미로, 웹 브라우저에서 실행 중인 스크립트가 다른 출처(Origin)의 리소스에 접근할 때 적용되는 보안 메커니즘입니다.### 출처(Origin)..
카카오 디벨로퍼 접속https://developers.kakao.com/ Kakao Developers카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.developers.kakao.com 애플리케이션 생성내 애플리케이션 > 애플리케이션 추가하기애플리케이션 정보를 입력하고 애플리케이션을 추가합니다. 플랫폼 등록생성한 애플리케이션 클릭 > 좌측 사이드바 '플랫폼' 클릭 > Web 플랫폼 등록 클릭카카오 JS SDK를 사용할 도메인을 입력합니다.필자는 배포 도메인, 개발 도메인을 설정 후 저장하였습니다. 환경변수 설정NEXT_PUBLIC_KAKAO_SDK_KEY={애플리케이션의 JavaScript 키}예시) NEXT_..
배경Next.js로 개발한 템플릿 기반 웹 애플리케이션에서 하이드레이션 오류를 경험했습니다.이 애플리케이션은 다양한 블록 타입(텍스트, 이미지, 캘린더 등)을 포함한 템플릿을 렌더링하는 기능을 가지고 있었습니다. 특히 문제가 발생한 부분은 캘린더 컴포넌트였습니다. 서버에서 렌더링된 HTML과 클라이언트에서 렌더링된 HTML이 일치하지 않아 다음과 같은 오류 메시지가 발생했습니다.Error: Hydration failed because the server rendered HTML didn't match the client.오류 메시지를 자세히 살펴보면, 캘린더 컴포넌트의 ID 값과 클래스 이름이 서버와 클라이언트에서 다르게 생성되고 있었습니다.특히 날짜 관련 데이터(`Date` 객체)를 사용하는 부분에서 ..
미리보기 배경프론트엔드 개발 시 전체 화면에 폭죽 애니메이션이 실행되는 요소처럼 화면 최상단에 위치하고 화면 전체를 차지하는 요소를 사용해야하는 상황이 있습니다. (이하 최상위 요소로 표현합니다.)이 때, 최상위 요소는 최상단에 위치하며 화면 전체를 차지하고 있기 때문에 최상위 요소 뒷부분의 요소들을 클릭하고 싶어도 모든 이벤트가 가로막히게 됩니다.이에 최상위 요소를 뚫고 이벤트(클릭 등)를 통과시키는 기능이 필요하며 이는 최상위 요소 css에 아래 속성을 추가하여 간단하게 구현할 수 있습니다. 코드// 아래 속성을 최상위 요소에 삽입합니다.pointer-events: none;// 예시 안녕? 나 최상위 요소 ^-^
코드1. 이미지 원본 비율 유지하기const PreviewImage = styled(Image)` width: auto; height: auto;`; 2. 부모 요소 너비만큼 채우기 const PreviewContainer = styled(Box)` width: 300px; min-height:300px;`;const PreviewImage = styled(Image)` width: auto; height: auto; object-fit:contain; // 이미지 비율을 유지하기 object-fit:cover; // 부모 요소 너비 꽉 채우고 넘치는 부분을 잘라내기`; 참고자료https://nextjs.org/docs/pages/api-reference/components/imag..
// 유튜브 링크 형식 변환 function formatVideoLink(link: string) { // 정규식 설명: // ^@? - 문자열 시작, @ 기호 포함 가능 (선택적) // (?:https?:\/\/)? - URL의 시작 부분, http:// 또는 https:// (선택적) // (?:www\.)? - www. 부분 (선택적) // (?:youtube\.com\/watch\?v=|youtu\.be\/) - 유튜브 도메인과 경로 패턴 (2가지 형식) // ([a-zA-Z0-9_-]{11}) - 유튜브 비디오 ID (11자리 영숫자, 언더스코어, 하이픈) // (?:[?&]si=[^&]*)? - ?si= 또는 &si= 파라미터와 그 값 (선택적) c..
const FileSelectButton = () => { const [file, setFile] = useState(null); function handleFileSelectChange(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (file) { setFile(file); } } return ( 최대 10MB 이하 .jpg, .png 첨부가능 );};export const FileSelectButton_Con..
/** * 랜덤 닉네임 생성 유틸리티 * 형용사와 명사를 조합하여 독특한 닉네임을 생성합니다. */// 형용사 목록const adjectives = [ "행복한", "슬픈", "화난", "지친", "활기찬", "조용한", "시끄러운", "따뜻한", "차가운", "부드러운", "강한", "약한", "빠른", "느린", "밝은", "어두운", "현명한", "용감한", "겸손한", "정직한", "친절한", "엄격한", "귀여운", "멋진", "신비로운", "공정한", "냉철한", "신중한", "논리적인", "객관적인", "분석적인", "통찰력있는", "예리한", "진지한", "사려깊은", "정의로운", "합리적인", "균형잡힌", "엄정한"..