React

Next.js typescript mysql styled-components 셋팅

martinooo 2023. 3. 3. 14:36
728x170


Next.js typescript 프로젝트 구축, 보일러 플레이트처럼 만들어 놓고 쓰면 좋을꺼 같다.


 

Github Source ☝ 구현 소스를 보실수 있습니다.

https://github.com/yoogukhyeon/next-boilerplate

 

GitHub - yoogukhyeon/next-boilerplate

Contribute to yoogukhyeon/next-boilerplate development by creating an account on GitHub.

github.com

 


structure 구조
pages, components, config, public, styles, .babelrc

  • pages: next에서 auto routing 되는 페이지를 생성한다.
  • components: 재사용 가능한 components 생성한다.
  • config: mysql 셋팅한다.
  • public: font 셋팅 & img 셋팅
  • styles: styled-component로 작성한 글로벌 스타일 셋팅

 settings

styles => globals.ts 생성 및 초기 css 셋팅

import reset from 'styled-reset'; // style-reset 패키지
import { createGlobalStyle } from 'styled-components';

// GlobalStyles 설정
const GlobalStyles = createGlobalStyle` 
    ${reset}

    @font-face {
        font-family: "Pretendard";
        src: url('/fonts/Pretendard-Thin.woff2') format('woff2'), 
          url('/fonts/Pretendard-Thin.woff') format('woff');
        font-weight: 100;
    }
    @font-face {
        font-family: "Pretendard";
        src:url('/fonts/Pretendard-ExtraLight.woff') format('woff2'), 
         url('/fonts/Pretendard-ExtraLight.woff2') format('woff');
        font-weight: 200;
    }
    @font-face {
        font-family: "Pretendard";
        src:url('/fonts/Pretendard-Light.woff2') format('woff2'), 
         url('/fonts/Pretendard-Light.woff') format('woff');
        font-weight: 300;
    }
    @font-face {
        font-family: "Pretendard";
        src:url('/fonts/Pretendard-Regular.woff2') format('woff2'), 
         url('/fonts/Pretendard-Regular.woff') format('woff');
        font-weight: 400;
    }
    @font-face {
        font-family: "Pretendard";
        src:url('/fonts/Pretendard-Medium.woff2') format('woff2'), 
         url('/fonts/Pretendard-Medium.woff') format('woff');
        font-weight: 500;
    }
    @font-face {
        font-family: "Pretendard";
        src:url('/fonts/Pretendard-SemiBold.woff2') format('woff2'), 
         url('/fonts/Pretendard-SemiBold.woff') format('woff');
        font-weight: 600;
    }
    @font-face {
        font-family: "Pretendard";
        src:url('/fonts/Pretendard-Bold.woff2') format('woff2'), 
         url('/fonts/Pretendard-Bold.woff') format('woff');
        font-weight: 700;
    }
    @font-face {
        font-family: "Pretendard";
        src:url('/fonts/Pretendard-ExtraBold.woff2') format('woff2'), 
         url('/fonts/Pretendard-ExtraBold.woff') format('woff');
        font-weight: 800;
    }
    @font-face {
        font-family: "Pretendard";
        src:url('/fonts/Pretendard-Black.woff2') format('woff2'), 
         url('/fonts/Pretendard-Black.woff') format('woff');
        font-weight: 900;
    }

    html,
    body {
      padding: 0;
      margin: 0;
    }
    html{
      font-size: 21px; //10px
    }
    @media (min-width: 767px){
      html{
        font-size: 16px;
      }
    }
    @media (max-width: 320px){
      html{
        font-size: 14px;
      }
    }

    body{
      font-size: 21px;
    }
    body a {
      color: inherit;
      text-decoration: none;
    }
    body em{
      font-style: normal;
    }
    body ul, body li{
      list-style: none;
    }
    //icon용
    body i{
      display: flex;
      align-items: center;
    }
    //이미지 렌더링 오류 수정
    body img{
      image-rendering: -webkit-optimize-contrast;
      transform: translateZ(0);
      backface-visibility: hidden;
    }
    body b{
      font-weight: bold;
    }
    body * {
      line-height: 1;
      box-sizing: border-box;
      padding: 0;
      margin: 0;
      font-family: 'Pretendard','sans-serif';
    }
    body input,  body textarea { 
      -moz-user-select: auto;
      -webkit-user-select: auto;
      -ms-user-select: auto;
      user-select: auto;
    }
    body input:focus {
      outline: none;
    }
    body button {
      border: none;
      background: none;
      padding: 0;
      cursor: pointer;
    }
`;

export default GlobalStyles;
_app.tsx는 서버로 요청이 들어왔을때 가장 먼저 실행되는 컴포넌트로 여기에 전역으로 공통 css 셋팅한다.

import GlobalStyles from '@/styles/globals';
import Head from 'next/head';
import type { AppProps } from 'next/app';

export default function App({ Component, pageProps }: AppProps) {
	return (
		<>
			<Head>
				<title>Quiz Web</title>
				<meta name="description" content="Quiz Web" />
				<meta name="viewport" content="width=device-width, initial-scale=1" />
				<link rel="icon" href="/favicon.ico" />
			</Head>
			<GlobalStyles />
			<Component {...pageProps} />
		</>
	);
}

_document.tsx 추가적인 styled-component를 사용할 경우 커스텀이 필요하다. 

해당 코드를 추가시켜야 SSR시에 styled가 헤더에 주입된다.
추가해주니 않으면 css가 적용되지 않고 먼저 렌더링 되면서 CSS 에러 현상이 발생한다. 

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 lang="ko">
				<Head />
				<body>
					<Main />
					<NextScript />
				</body>
			</Html>
		);
	}
}

export default MyDocument;

config: serverless-mysql 셋팅 

import mysql from 'serverless-mysql';

const pool = mysql({
	config: {
		host: process.env.DB_HOST,
		user: process.env.DB_USER,
		password: process.env.DB_PASSWORD,
		port: 3306,
		database: process.env.DB_DATABASE,
	},
});

export async function mainExcuteQuery({ query, values }: { query: string; values: any[] }) {
	try {
		const results = await pool.query(query, values);
		await pool.end();
		console.log('connected to db...');
		return results;
	} catch (error) {
		console.log('not connected to db...');
		return { error };
	}
}


pages > api에 기본 api 연동 
GET: http://localhost:3000/api/hello 테스트 하면된다. 

import { mainExcuteQuery } from '@/config/db';
import type { NextApiRequest, NextApiResponse } from 'next';

interface Data {
	hello: string;
}

export default async function handler(req: NextApiRequest, res: NextApiResponse<Data>) {
	const method: string | undefined = req.method;

	switch (method) {
		case 'GET':
			res.status(200).json({ hello: 'hello' });
			break;
		default:
			res.status(404).json({});
	}
}


.babelrc 셋팅
최초 SSR 이후 CSR로 라우팅을 하게되는데, 이때 서버에서 생성하는 해시값과 브라우저에서 생성하는 해시값이 
서로 달라서 에러가 발생하게 된다. 이를 해결하기 위해서 바벨을 셋팅한다. 

{
	"presets": ["next/babel"],
	"plugins": [
		[
			"babel-plugin-styled-components",
			{
				"ssr": true,
				"displayName": true,
				"preprocess": false
			}
		]
	]
}
그리드형