{
"compilerOptions": {
"target": "es2022", // 컴파일 할 ECMAScript 버전 ES6, ES2017, ES2018....
"lib": ["dom", "dom.iterable", "esnext"], // 컴파일 과정 사용될 라이브러리 파일 목록
"allowJs": true, // JS 파일 컴파일 허용 설정
"skipLibCheck": true /**/,
"baseUrl": "./", //프로젝트 내 모듈이 위치한 기준 디렉토리 설정
"path": {}, //baseUrl 기준으로 모듈을 불러올 위치 설정
"strict": true, // use-strict 엄격타입 설정
"typeRoot": ["./src/types/"], //사용자 설정 타입을 전역에서 사용 가능하도록 루트 폴더로 설정
"forceConsistentCasingInFileNames": true, //파일 참조시 대소문자 철저히 구별 설정
"noEmit": true /* 결과 파일을 Emit 여부 */,
"esModuleInterop": true, // 모든 imports에 대한 namespace 생성을 통해 CommonJS와 ES Modules 간의 상호 운용성이 생기게할 지 여부,
"module": "esnext", // 모듈을 위한 코드 생성 설정, amd, commonjs, none
"moduleResolution": "node", // 모듈 해석 방법 설정: 'node' (Node.js) 혹은
"resolveJsonModule": true, // json파일 Import시 설정
"isolatedModules": true, // 각 파일을 별도 모듈로 변환
"jsx": "preserve", //Next는 react대신 preserve로 설정
"incremental": true // 증분 컴파일 설정 여부
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], // 컴파일에 포함할 파일 목록
"exclude": ["node_modules"] //컴파일 제외 목록
}
이부분은 따로 설명할게 없다.. 공식문서를 확인하는 수밖에..
그래도 기본적인걸 몇가지 알아보자면 다음과 같다.
/** @type {import('next').NextConfig} */
module.exports = async (phase, { defaultConfig }) => ({
...defaultConfig,
/* 렌더링 가능한 페이지 확장 */
pageExtensions: ['ts', 'tsx', 'mdx', 'md', 'js', 'jsx'],
/* react Strict Mode */
reactStrictMode: true,
/* CSS-In-JS, 스타일드 컴포넌트 사용 시 */
compiler: {
styledComponents: true,
},
/* env 환경변수 사용시, process.env.customKey */
env: {
customKey: 'my-value',
},
/* 내장 image Optimization API 대신 외부 이미지 로더를 사용할때 */
images: {
loader: 'imgix, akami, cloudinary (택1, default: vercel)',
path: 'url...',
},
distDir: 'build', // 빌드시 .next폴더 대신 build 폴더 생성
/* 빌드 시 fs모듈 import 에러 문제 해결*/
/* 1. NextJS 12는 Webpack5사용, fs 모듈 Import 에러때문에 빡칠때 사용 */
/* 2. 기타 플러그인 필요시 사용 */
webpack5: true,
webpack: (config) => {
config.resolve.fallback = { fs: false };
config.module.rules.push({
test: /\.mdx/,
use: [
options.defaultLoaders.babel,
{
loader: '@mdx-js/loader',
options: pluginOptions.options,
},
],
});
return config;
},
devIndicators: {
buildActivityPosition: 'bottom-right',
},
});
_app.tsx
_app.tsx
는 모든 페이지 컴포넌트를 감싸고있는 최상위(root) 공통 컴포넌트이다. 그렇기 때문에 NextJS
앱을 실앻아면 가장 먼저 불러오는 파일이다.
_app.tsx
import '../styles/globals.css';
import type { AppProps } from 'next/app';
function MyApp({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />;
}
export default MyApp;
모든 페이지를 하나의 레이아웃 디자인으로 적용하기 위해선 아래와 같이 작성한다.
./Components/Layout.tsx
import Head from 'next/head';
import { ReactNode } from 'react';
type LayoutProps = {
children?: ReactNode;
};
const Layout = ({ children }: LayoutProps) => {
return (
<div>
<Head>
<title>{'공통 레이아웃'}</title>
</Head>
<section>
<header></header>
<main>
<div></div>
{children}
<div></div>
</main>
<footer></footer>
</section>
</div>
);
};
export default Layout;
_app.tsx
_app.tsx
에서 레이아웃 컴포넌트를 가져와서 감싸주면 된다.
import '../styles/globals.css';
import type { AppProps } from 'next/app';
import Layout from '../components/Layout';
function MyApp({ Component, pageProps }: AppProps) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
export default MyApp;
다중 레이아웃은 About
, Post
, Contact
등 다양한 페이지에서 서로 다른 디자인의 레이아웃으로 적용하려면, getLayout
이라는 기능을 추가해주기만 하면 된다. 공식문서에서 나온 방식으로 작성해준다.
import type { ReactElement, ReactNode } from 'react';
import type { NextPage } from 'next';
import type { AppProps } from 'next/app';
/* 각 페이지별 적용된 레이아웃 입력 및 반환 타입 지정 */
type NextPageWithLayout = NextPage & {
getLayout?: (page: ReactElement) => ReactNode;
};
/* 각 페이지를 컴포넌트 단위로 받을 커스텀 타입 */
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
};
export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
/* 페이지 전환시 서로 다른 레이아웃 적용(Nullish Coalescing) */
const getLayout = Component.getLayout ?? ((page) => page);
return getLayout(<Component {...pageProps} />);
}
_document.tsx
_document.tsx
파일을 생성해주면 _app
파일 다음으로 실행된다. 이 파일을 굳이 왜 생성해주는지 이유는 아래와 같다.
(carset, 웹 접근 관련 태그 설정...)
_Document
는 오직 서버(server)에서만 실행되므로 브라우저의 속성인 onClick()
같은 이벤트는 작동이 되지 않는다.<HTML>
,<HEAD>
,<MAIN>
,<NextScript>
는 필수로 들어가야한다.<HEAD>
를 넣을 떄 모든 페이지에 Head
를 적용하므로 반드시 next/document
를 import
한다.Head
태그 내부의 title
태그 같은 페이지별 변동될 만한 속성은 추가하지 않는다._document.tsx
import Document, {
DocumentContext,
Head,
Html,
Main,
NextScript,
} from 'next/document';
class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const initialProps = await Document.getInitialProps(ctx);
return initialProps;
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
Scss
,tailwind
,Emotion
등.. 자주 쓰이는 스타일링 라이브러리들이 존재하지만 얘네들은 딱히 _document
에서 설정할 것 없이 컨픽 파일(....json)
을 생성해주면 되지만, Styled-Component
를 사용해서 바로 스타일에 적용하기 위해서는 귀찮은 과정들을 거쳐야 했었다.
_Document
소스코드 수정import Document, {
Html,
Head,
Main,
NextScript,
DocumentContext,
} from 'next/document';
import { ServerStyleSheet } from 'styled-components';
class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} finally {
sheet.seal();
}
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
export default MyDocument;
.babelrc
추가NextJS
를 제작한 Vercel에선 기본 컴파일로러 해석하기 위해 옵션을 추가하고 있지만, 직접 적용해보니 완벽하게 적용되지는 않았다.
{
"presets": ["next/babel"],
"plugins": [
["styled-components", { "ssr": true, "displayName": true, "pure": true }]
]
}
아직 완벽하지 않다고 설명하지만, 이정도만 설정해줘도 나한텐 충분한것 같다.
next.config.js
에서 옵션을 추가하기만 하면된다.
next.config.js
compiler: {
styledComponents: true,
},
_error.tsx
처음에 생성되지 않는 이 파일은, 요청시 발생하는 각 상태코드 400 Series
, 500 Series
등 개발자의 입맛대로 디자인된 에러페이지를 구현하기 위한 파일이다.
각 에러마다 페이지 파일을 생성하는 것 보단, 대응하는 하나의 파일로 통합하면 더 효율 적일 것이다. 그래서 _error.tsx
도 각 에러 발생시 하나의 파일로 통합하는 예제가 나와있다.
import WithLayout from 'components/common/Layout/WithLayout';
import { NextPage, NextPageContext } from 'next';
interface Props {
statusCode?: number;
}
const Error: NextPage<Props> = ({ statusCode }) => {
return (
<WithLayout>
<p>
{statusCode
? `An error ${statusCode} occurred on server`
: 'An error occurred on client'}
</p>
</WithLayout>
);
};
Error.getInitialProps = ({ res, err }: NextPageContext) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
return { statusCode };
};
export default Error;