미들웨어
// middleware.ts
export const config = {
matcher: [`/((?!favicon.ico|_next/image|_next/static).{1,})`],
};
export default async function (req: NextRequest) {
NextResponse.next();
}
next.js의 서버로 들어오는 요청이 어플리케이션 기능으로 넘어가기 전에 전처리할 수 있다. 모든 요청들이 먼저 처리되어 지나가야 하기 때문에 공통적으로 처리되어야할 사항들을 작성할 수 있다.
미들웨어 함수에서는 req 파라미터를 통해 요청을 파싱할 수 있으며 NextResponse.next()
함수를 사용하여 요청을 어플리케이션으로 넘길 수 있다. config의 경우 미들웨어 함수를 거치게 할 URL을 설정할 수 있다.
.
├── app
├── components
└── middleware.ts
사전 정의된 파일 middleware.ts를 프로젝트 루트에 정의함으로써 기능을 사용할 수 있다.
Instrumentation
실험적 기능이었던 instrumentation 기능이 15버전에서는 정식기능으로서 안정적으로 사용할 수 있게 되었다. instrumentation 이라는 이름에서 알 수 있듯이 원래는 telemetry, sentry와 같은 모니터링 도구계측 도구를 불러오기 위해 작성되었음을 짐작할 수 있다.
instrumentation.ts:
export const register = async () => {
if ("nodejs" === process.env.NEXT_RUNTIME) {
// your code…
}
if ("edge" === process.env.NEXT_RUNTIME) {
// your code…
}
};
register 함수를 통해 nextjs 서버에 등록되어 작동한다. 전체 서버에서 한 번만 호출되는 것이 아니고 런타임 별로 한 번씩 호출되기 때문에 nodejs와 edge 코드를 분리하여 구성해야 예기치 못한 오류를 줄일 수 있다.
한 번만 호출된다는 점 때문에 서버 부트업시 환경변수나 싱글톤을 유지하기 위하여 또는 전역변수를 초기화 하기 위하여 사용될 수도 있지만 본래의 목적과 다르기 때문에 이곳에 작성하는 것은 추천하지 않는다.
싱글톤의 경우라면 nodejs import 구문이 자체적으로 지원하기 때문에 파일을 나누어 관리하면 되고, 되도록이면 동시제어 오류가 예상되는 전역변수는 사용하지 않는 것이 좋다. 환경변수의 경우 모두 .env로 별도 관리하는 것을 추천한다.
.
├── app
├── components
├── middleware.ts
└── instrumentation.ts
사전 정의된 파일 instrumentation.ts를 프로젝트 루트에 정의함으로써 기능을 사용할 수 있다.
환경변수
환경변수는 .env 파일로 구성할 수 있다. 서버가 실행될 때 로드되며 NODE_ENV에 따라서 불러오는 파일이 서로 다르다. .env, .env.development, .env.production 으로 나뉘며 모두 접미사로 .local을 붙일 수 있다.
예를 들어 .env.development의 경우 NODE_ENV의 값이 development일 때 읽어와 초기화 되는데 .env.development.local이 있는 경우 추가로 읽어들여 동일한 변수값이 있다면 덮어씌워진다. 이것은 production일 때 그 파일명에 맞춰 동일한 방식으로 작동한다. .env의 경우 NODE_ENV 값과 상관없이 읽어들인다.
읽어들이는 순서는 조금 지저분한데 .env, .env.development 의 순서로 읽어들이고 그다음 접미사 .local이 있는 파일을 동일한 순서로 읽어들인다. 예를들면 .env -> .env.development -> .env.local -> .env.development.local 의 순서이다.
# .env.development
SITE_NAME=helloworld
NEXT_PUBLIC_CLIENT_VARIABLE=helloclient
luasenvy@luasenvy:~$ npm run dev
▲ Next.js 15.1.4
- Local: http://localhost:3000
- Network: http://192.168.1.2:3000
- Environments: .env.development.local, .env.local, .env.development, .env
✓ Starting...
✓ Ready in 1732ms
서버를 실행해보면 읽어들인 환경변수 파일 목록을 확인할 수 있다. 서버 환경SSR을 포함에서는 process.env.SITE_NAME 을 통하여 값을 사용할 수 있지만 *"use client"*와 같이 클라이언트 환경에서 동작하는 어플리케이션에서는 서버 환경변수가 보안상 노출되지 않기 때문에 사용할 수 없다.
접두사 NEXT_PUBLIC_을 활용하면 클라이언트 환경에서도 사용할 수 있도록 할 수 있지만 이 경우에는 환경변수 값이 빌드 결과에 문자열 형태로 "helloclient" 라는 값 그대로 포함되기 때문에 마찬가지로 보안상 주의하여 사용해야 한다. 또한, 환경변수를 사용할 위치에 정확히 process.env.NEXT_PUBLIC_CLIENT_VARIABLE과 같이 명시해야 하기 때문에 코드가 지저분해질 수 있다.
보안상 문제가 없는 값이라면 써도 좋지만 개인적으로는 개발/관리상 편의를 위하여 모두 서버 환경변수로 선언하여 SSR 환경을 통해서만 접근하고 클라이언트 환경의 경우 컴포넌트로 분리하여 파라미터로 전달해주는 방식을 선호한다.
.
├── app
├── components
├── .env
├── .env.local
├── .env.development
├── .env.development.local
├── .env.production
├── .env.production.local
├── middleware.ts
└── instrumentation.ts
사전 정의된 파일 .env 등을 프로젝트 루트에 정의함으로써 기능을 사용할 수 있다.
너무 복잡하다면
- .env.development: 개발 환경변수
- .env.production: 운영 환경변수
- .env.development.local: 개발자 개인용 환경변수
이렇게 세개만 기억하고 .gitignore에 .env.local*을 등록해두자.
스타일 모듈
스타일 모듈은 CSS를 모듈로써 import하고 활용할 수 있는 방안을 제공한다. component.module.css와 같이 접미사 .module.css를 통하여 명시할 수 있다. 이러한 CSS 파일들은 관례적으로 styles 디렉토리 하위에 위치시켜 관리하나 nextjs에서 사전 정의하여 제공해주는 디렉토리는 아니다.
.
├── app
│ ├── api
│ ├── first
│ ├── layout.tsx
│ └── page.tsx
├── components
│ └── Counter.tsx
└── styles
└── counter.module.css
/* styles/count.module.css */
.counter {
background-color: #0070f3;
color: white;
border: none;
padding: 0.5rem 1rem;
border-radius: 5px;
cursor: pointer;
font-size: 1rem;
font-weight: bold;
&> span {
margin-left: 3rem;
}
}
// components/Counter.tsx
"use client";
import { useState } from "react";
import styles from "@/styles/counter.module.css";
export default function Counter () {
const [count, setCount] = useState<number>(0);
const handleClickButton = () => setCount((prev) => prev + 1);
return <button type="button" onClick={handleClickButton} className={styles.counter}>{count}</button>
}
클래스 셀렉터를 기준으로 모듈로 가져오기 때문에 css를 작성할 때에도 클래스를 기준으로 모듈 처럼 작성하는 것이 관리하기 좋다. 예를 들어 #counter와 같이 id 셀렉터나 button 같은 태그 셀렉터는 작동하지 않는다. className 속성에 스타일 모듈을 전달함으로써 설정할 수 있다.