React
Next.js typescript mysql styled-components 셋팅
martinooo
2023. 3. 3. 14:36
Next.js typescript 프로젝트 구축, 보일러 플레이트처럼 만들어 놓고 쓰면 좋을꺼 같다.
Github Source ☝ 구현 소스를 보실수 있습니다.
https://github.com/yoogukhyeon/next-boilerplate
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
}
]
]
}