페이지 이동 후 쿠키가 사라지는 문제(Next.js, setHeader, writeHead)

설정한 쿠키가 페이지 이동 후 사라질 때 의심해 볼 상황

getServerSideProps에서 writeHead 또는 setHeader로 쿠키를 설정하고 redirect 시

등록된 쿠키가 사라지는 상황이 있다.

redirect된 페이지에서 쿠키가 사라진다면 헤더 설정의 문제를 확인해 봐야 한다.

합리적으로 의심해 볼 수 있는 문제는 페이지 이동 후 쿠키가 사라졌으므로 쿠키의 사용 범위에 대한 설정 문제이다.

Path=/ 설정은 쿠키가 모든 경로에서 유효하도록 설정하는 옵션이다. 이 옵션을 통해 모든 페이지에서 쿠키에 접근할 수 있도록 설정하므로 해당 옵션이 누락되면 페이지 이동 시 쿠키가 사라지는 문제가 발생한다.

setHeader와 writeHead를 사용해 path=/를 포함하는 쿠키 설정 예제를 확인해보자.
HttpOnly는 클라이언트의 자바스크립트에서 쿠키에 접근할 수 없도록 설정하는 옵션이다.

export async function getServerSideProps({ req, res }) {

  res.setHeader('Set-Cookie', 'myCookie=value; Path=/; HttpOnly; Max-Age=3600');

  return {
    redirect:{
      destination: '/new-page', // redirect할 페이지
      permanent: false
    }
  }
}

writeHead의 사용도 확인해보자.

export async function getServerSideProps({ req, res }) {
  const cookieVal = 'myCookie=value; Path=/; HttpOnly; Max-Age=3600';

  res.writeHead(302, {
    Location: 'new-page', // redirect할 페이지
    'Set-Cookie': cookieVal,
    'Custom-Header': 'Custom', // 커스텀 헤더
  });
  
  res.end();

  return {
    props: {}
  };
}

writeHead의 첫 번째 파라미터는 HTTP 응답 상태 코드, 두 번째는 헤더 객체를 설정한다.

헤더 객체 내부의 Location 필드로 redirect 페이지를 설정할 수도 있고, return 문에 redirect를 전달하면서 페이지를 설정할 수도 있다.

Next.js의 기본 권장 방식은 return 문에서 redirect를 전달하는 방법이다.

그렇다면 writeHead와 setHeader의 차이점은 무엇일까?

  • writeHead는 상태 코드를 설정(setHeader는 불가능)할 수 있는 등의 세세한 설정을 할 수 있다.
  • setHeader는 응답이 시작(res.end())되기 전 여러 번 호출이 가능하지만 writeHead는 한번만 호출한다.

따라서 상태 코드와 여러 헤더를 동시에 설정하려면 writeHead, 개별적으로 헤더를 추가하고 수정할 때는 setHeader를 사용하는 것이 좋다.

이와 같이 Path=/를 추가하면 이동하는 페이지에 상관없이 애플리케이션에서 쿠키를 확인할 수 있다.


추가 참고 : Next.js 공식 문서(Setting Headers)

React에서 iframe 전체화면 전환하기(속성 체크 포함)

allowfullscreen 그리고 document.fullscreenEnabled

화면 내부에 삽입된 iframe에서 전체 화면 설정, 해제 기능을 구현하려고 한다.
구현은 생각보다 간단하다.

먼저 iframe 태그에 allowfullscreen 속성을 추가해야 한다.
allowfullscreen 속성을 갖는 iframe은 자바스크립트에서 제공하는 메서드를 통해 전체화면 설정, 해제가 가능하며 ESC도 사용할 수 있다.

<iframe src=”https://choonse.com” allowfullscreen />

전체 화면 설정 메서드 document.documentElement.requestFullscreen()
전체 화면 해제 메서드 document.exitFullscreen()

전체화면 설정/해제 버튼에 이벤트를 걸어서 사용할 수 있으며, 반환형은 Promise이다.

만약 allowfullscreen 속성이 없는 iframe에서 해당 메서드를 호출하면 어떻게 될까?disallowed by permissions policy 에러로 사람을 당황시킨다.

이 때는 allowfullscreen 속성의 존재 여부(true)를 체크해주면 되는데, 해당 속성은
document.fullscreenEnabled 속성을 사용해 true/false로 확인한다.

만약 iframe에 allowfullscreen 속성이 없을 때 전체 화면 버튼을 비활성화하고 싶을 때는 어떻게 할까?

document.fullscreenEnabled를 체크해서 활성/비활성을 결정하면 되는데 document is not defined와 같은 에러를 피하기 위해서는 useEffect 내부에서 해당 작업을 처리하여 렌더링이 완료된 시점에 document에 접근하도록 해야 한다.

Next.js + Leaflet(OSM) Marker 표시하기

Next.js에서 Leaflet Marker 이미지 로딩하기

React-leaflet에서 제공하는 기본 설정 방법에 따라 leaflet 코드를 구현하여도 맵은 표시되지만 Marker 이미지는 깨져서 표시됩니다.

Next.js에서 이미지는 Next/image와 이미지 상대 주소를 import하여 사용하면 잘 로딩이 되지만 상대 경로 url을 직접 사용하면 작동하지 않습니다.

예를 들면 다음과 같은 상황입니다.

import Image from "next/image";
import logo from "../../styles/images/logo.png";

//작동함
<Image src={logo} alt="logo />

//작동하지 않음
<Image src={"../../styles/images/logo.png"} alt="logo" width="100px" height="100px" />

이는 Next.js에 정해진 폴더 규칙이 있기 때문인데요. 만약 경로를 사용해 이미지나 파일을 가져오고 싶다면 public 폴더를 이용해야 합니다.

빌드 후 기본 폴더는 public이므로 public 폴더 내 logo.png 파일을 넣는 경우 /logo.png로 접근이 가능합니다.

import Image from "next/image";

<Image src={"/logo.png"} alt="logo" width={"100px"} height={"100px"} />

Marker 이미지 표시하기

이를 참고하면 Marker 이미지 부분도 icon을 사용해서 응용할 수 있습니다.

public 폴더 내 images 폴더를 만들고 logo.png 파일을 넣은 뒤 다음과 같이 사용합니다.

import { MapContainer, TileLayer, useMap, Marker, Popup } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import { icon } from "leaflet";

const Icon = icon({
  iconUrl: "images/logo.png",
  iconSize: [24, 24],
  iconAnchor: [12, 24],
});

const MyMap = () => {
  return (
    <MapContainer
      center={[37.56675, 126.97842]}
      zoom={10}
      scrollWheelZoom={true}
      style={{ width: "500px", height: "500px" }}
    >
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[37.56675, 126.97842]} icon={Icon} >
        <Popup>서울 시청</Popup>
      </Marker>
    </MapContainer>
  );
};

export default MyMap;

Next.js + Leaflet(OpenStreetMap) 초기 설정하기

Next.js와 leaflet이 만나기 위해서는 참고해야 할 사이트가 많습니다.

leafletjs.com
react-leaflet.js.org
openstreetmap.org

간략하게 정리해보겠습니다.


1. 라이브러리 설치

Next.js + Typescript는 설치되었다고 가정하겠습니다.(관련 포스팅 클릭)

npm i leaflet react-leaflet

Typescript 지원을 위한 라이브러리도 설치합니다.

npm i -D @types/leaflet

2. 코드 구현하기

다음과 같이 컴포넌트를 생성합니다.

import “leaflet/dist/leaflet.css”; 를 설정하지 않으면 맵이 깨져서 표시가 되고 style에 사이즈를 설정하지 않으면 하얀 화면만 나오니 두 부분 모두 주의해야 합니다.

scrollWheelZoom은 스크롤 확대/축소 기능을 설정합니다.

import { MapContainer, TileLayer, useMap, Marker, Popup } from "react-leaflet";
import "leaflet/dist/leaflet.css";

const MyMap = () => {
  return (
    <MapContainer
      center={[37.56675, 126.97842]}
      zoom={10}
      scrollWheelZoom={true}
      style={{ width: "500px", height: "500px" }}
    >
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[37.56675, 126.97842]}>
        <Popup>서울 시청</Popup>
      </Marker>
    </MapContainer>
  );
};

export default MyMap;

별도의 컴포넌트를 생성하는 이유는 관리를 위한 분리도 있지만 Next.js의 특성상 서버 렌더링 시 window 전역 객체에 접근할 수 없는 문제로 인해 발생하는 에러를 해결하기 위해서입니다.

DOM이 생성된 뒤 실행되는 useEffect를 사용하거나 레이지 로딩 기능인 dynamic을 사용할 수 있으며 여기서는 dynamic 기능을 사용하겠습니다.

같은 위치에 다음 컴포넌트를 생성합니다.

import dynamic from "next/dynamic";

const MyMap = dynamic(() => import("./MyMap"), { ssr: false });

const ShowMap = () => {
  return <MyMap />;
};

export default ShowMap;

이것으로 기본 구현은 완료되었으며 다음과 같이 맵을 호출하면 됩니다.

<ShowMap />

결과는 다음과 같습니다.

이것으로 Next.js에서 Leaflet을 사용하기 위한 설정이 완료되었지만 Marker 이미지가 깨지는 현상이나 언어 설정 등 추가할 부분이 많습니다.

해당 내용은 다른 포스트에서 다루도록 하겠습니다.

Next.js + Typescript + Emotion + Tailwind 환경 구축하기

Next.js에서 Emotion과 TailwindCSS를 함께 사용하기 위한 설정

1. Next.js 설치

다음 명령을 사용해 Next.js의 최신 버전 + typescript를 설치한다.

npx create-next-app@latest --ts

아래 명령어로 설치 및 버전을 확인한다.

npx next -v

2. emotion 관련 라이브러리 설치

emotion 관련 라이브러리를 설치한다.

npm i @emotion/react @emotion/styled @emotion/css @emotion/server

3. tailwind, twin.macro 라이브러리 설치

tailwind, twin.macro 관련 라이브러리를 설치하고 devDependencies에 추가한다.

npm i twin.macro tailwindcss postcss@latest autoprefixer@latest @emotion/babel-plugin babel-plugin-macros --save-dev

config 파일 생성을 위해 다음 명령어를 사용한다.

tailwind.config.js를 사용하면 입맛에 맞게 사용자 지정 스타일을 사용할 수 있다.

npx tailwindcss init -p
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./pages/**/*.{js,ts,jsx,tsx}",
    "./components/**/*.{js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};

styles/globals.css의 상단에 다음 코드를 추가한다. (HTML 태그 내에서 인라인으로 tailwind를 사용 가능하게 함)

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

4. .babelrc 생성

twin.macro의 사용이 가능하도록 plugin 설정을 해야 하므로 .babelrc 파일을 생성한다.

내부 설정은 다음과 같다.

{
  "presets": ["next/babel"],
  "plugins": ["babel-plugin-macros"]
}

5. 작동 테스트

emotion 및 emotion +tw(tailwind) 작동을 테스트한다.

아래 코드를 pages/index.tsx에 작성하고 npm run dev로 실행한다.

import type { NextPage } from "next";
import styles from "../styles/Home.module.css";
import styled from "@emotion/styled/macro";
import tw from "twin.macro";

const Input = tw.input`
    text-center border h-28
`;

const MyDiv = styled.div`
  background: gold;
  font-size: 5rem;
  margin-top: 10px;
`;

const Home: NextPage = () => {
  return (
    <div className={styles.container}>
      <main className={styles.main}>
        <h1 className={styles.title}>
          <Input placeholder="box" />
          <MyDiv>Test Text</MyDiv>
        </h1>
      </main>
    </div>
  );
};

export default Home;

결과는 다음과 같다.


관련 링크

nextJS https://nextjs.org/

tailwindCSS https://tailwindcss.com/

emotion https://emotion.sh/docs/introduction

twin.macro https://github.com/ben-rogerson/twin.macro#readme