이것 저것 하려다 보니 기본적으로 들어가야 하는 것들이 더 필요한 것 같아.
1. 기본 Layout 설정해두는 거랑
2. Mui appbar랑 footer정도 만들어서 스토리북에 넣어두고(나중에 지우더라도)
3. storybook에 mui addon이 있네? 그것도 설치해볼거야. 해봤는데 뭐가 문제인지 잘 안되는 거 같더라구 ㅎㅎ
next.js하려면 기본적으로 layout이 있어야겠지? 그치? 아닌가? ㅎㅎ 몰라 난 처음해보는 거라 ㅎ 그런데 뭐 세상에 정답이 어디있겠어? ㅋㅋㅋ next.js 문서 정독했는데 졸면서 읽었으니 다 까먹었겠지? ㅎ 그래서 layout 파트를 다시 읽어봤어.
next.js 문서에서 소개하는 layout 방식은 두 가지가 있네. 하나는 _app.tsx에서 header footer같은 컴포넌트 다이렉트로 때려박는 방법. 그럼 전체 page가 단일 layout을 사용하게 되겠지. 다른 하나는 getLayout 이라는 property를 넣어서 page별로 layout을 지정하는 방법이 있지. 웹 페이지가 단일 layout으로 구성되는 경우는... -_- 있나? 로그인 페이지에 메뉴랑 사이드 바랑 그런거 있으면 이상하잖아? ㅋ 아니라고 생각하는 사람은 간편하게 첫번째 방식으로 고고
암튼 이 글에서는 페이지별로 layout을 구현하도록 할거야. 우선 _app.tsx를 변경해야해.
import * as React from "react";
import Head from "next/head";
import { AppProps } from "next/app";
import { ThemeProvider } from "@mui/material/styles";
import CssBaseline from "@mui/material/CssBaseline";
import { CacheProvider, EmotionCache } from "@emotion/react";
import theme from "../src/theme";
import createEmotionCache from "../src/createEmotionCache";
import { NextPage } from "next";
// import GlobalStyles from "./../styles/GlobalStyles";
// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();
interface MyAppProps extends AppProps {
emotionCache?: EmotionCache;
Component: NextPageWithLayout;
}
/*For Per-page layout! */
type NextPageWithLayout = NextPage & {
getLayout?: (page: React.ReactElement) => React.ReactNode;
};
export default function MyApp(props: MyAppProps) {
const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
const getLayout = Component.getLayout ?? ((page) => page);
return getLayout(
<CacheProvider value={emotionCache}>
<Head>
<meta name="viewport" content="initial-scale=1, width=device-width" />
</Head>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
{/* <GlobalStyles /> */}
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
}
특별한 건 없어. typescript사용하니까 getlayout 함수를 사용하기 위한 type을 좀 만들어 주고 getlayout함수로 전체 컴포넌트를 감싸 주는거지. 아래 한 부분만 보면 page component에 layout이 있으면 그걸 쓰시구요. 아니면 page를 그대로 리턴해주세요~ 라고 되어 있잖아? 이제 각각 page component에 getLayout을 정의해 주면 되겠네.
const getLayout = Component.getLayout ?? ((page) => page);
자 이제 index.tsx에서 어떻게 하는지 보자구. 쓸데없는 것들 좀 정리했고 getServerSideProps는 그대로 놔뒀어 ㅎㅎ 맨날 까먹으니까~ 익숙해지면 템플릿에서 지우도록 하자고 ㅎㅎ
import type { NextPage } from "next";
import Head from "next/head";
import { Container } from "@mui/material";
import tw, { styled } from "twin.macro";
import React, { ReactElement, ReactNode } from "react";
import Layout from "../components/Layout";
export const getServerSideProps = async () => {
const res = await fetch("http://localhost:3000/api/hello");
const data = await res.json();
if (!data) {
return {
notFound: true,
};
}
return {
props: data,
};
};
type NextPageWithLayout<T> = NextPage<T> & {
getLayout?: (page: ReactElement) => ReactNode;
};
const MainContainer = styled(Container)(() => [tw`text-center`]);
const Home: NextPageWithLayout<{ name: string }> = ({ name }) => {
return (
<MainContainer>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<h1>안녕 여러분!!</h1>
</main>
</MainContainer>
);
};
export default Home;
Home.getLayout = function getLayout(page: ReactElement) {
return <Layout>{page}</Layout>;
};
그리고 _app.tsx하고 마찬가지로 getLayout 추가한 type을 새로 만들었고 (이거 여러 페이지에서 사용할테니까 다른 파일로 빼내야겠다.) 그리고 맨 마지막에 getLayout 설정하고 page에 맞는 layout으로 감싸서 페이지를 리턴하면 되지. 이제 앞으로 모든 페이지는 이런 페턴으로 만들어주면 돼.
자 그럼 이제 간단한 <Layout>을 하나 만들어 보자구.👇 우선 components폴더에 파일을 총 3개 만들었어.
Layout.tsx
import React from "react";
import MainAppBar from "./MainAppBar";
import MainFooter from "./MainFooter";
const Layout: React.FC<any> = ({ children }) => {
return (
<>
<MainAppBar />
{children}
<MainFooter />
</>
);
};
export default Layout;
그야말로 Layout 컴포넌트. MUI의 appbar랑 기존 next.js footer를 활용해서 MainAppBar랑 MainFooter를 만들고 위 아래로children(페이지)를 감싸주는 형태로 했어. MainAppBar랑 MainFooter는 github 참고하자 너무 길어~ 이렇게 하고 나면 아래와 같은 결과~ 요런식으로 해주면 이제 슬슬 뭔가 보인다 그치?
자 협업을 컴포넌트 드리븐!! 스토리북에도 넣자고. 세개 전부 다. MainAppbar랑 MainFooter 그리고 Layout까지! 우선 기존 example 싹 다 지워주고 stories/components 폴더에 스토리 파일 3개 추가하자구~ 다 해봤잖아? ㅎㅎㅎ
Layout.stories.tsx, MainAppBar.stories.tsx, 그리고 MainFooter.stories.tsx 만들고 각각 내용을 채워주자구~ MyButton.stories.tsx 만들어 둔거 참고하면 쉽잖아? MainAppBar.stories.tsx 하나만 보자구.(나머지는 git 참고)
import { ComponentStory } from "@storybook/react";
import MainAppBar from "../../components/MainAppBar";
export default {
title: "Components/AppBar",
component: MainAppBar,
};
const Template: ComponentStory<typeof MainAppBar> = (args) => (
<MainAppBar {...args} />
);
export const Default = Template.bind({});
Default.args = {};
뭐 설명할게 없다 그치? 스토리북 하라는 데로 하는겨 나도 ㅎㅎ 암튼 이렇게 하면 스토리북 브라우저에서 아래와 같이 추가된 걸 볼 수 있지👇👇
스토리북 Layout 모습도 잘 보이고 말이지.
자 그런데 Pages에서 Home을 보면 이런게 하나도 없어. Layout이 하나도 설정이 안된거지. 스토리북에는 _app.js 이런게 적용이 안되니까 말이야. 그럼 여기에는 _app.js에서 콜되는 getLayout을 어떻게 불러오지?
몰라 ㅋ 그래서 내 맘대로 할거야. storybook 파일에서 아래처럼 무식하게 감싸줘도 되는데. Layout이 page 별로 변경되거나 다른 레이아웃을 사용하면 스토리북도 같이 바꿔야 하잖아? 언제 그렇게 해 귀찮게.
<Layout>
<Home {...args} name={name} />
</Layout>
그래서 아래처럼 home.stories.tsx를 변경했어
import { ComponentStory } from "@storybook/react";
import Home, { getServerSideProps } from "../../pages/index";
export default {
title: "Pages/Home",
component: Home,
};
export const HomePage: ComponentStory<typeof Home> = (
args,
{ loaded: { name } }
) => {
const getLayout = Home.getLayout ?? ((page) => page);
return getLayout(<Home {...args} name={name} />);
};
HomePage.args = { name: "John Doe" }; //default args
HomePage.loaders = [
async () => {
let data = await getServerSideProps();
return data.props;
},
];
_app에서 하는 기능 그대로 따온거야. getLayout 불러와서 있으면 그거 반환 없으면 그냥 페이지 반환. 이렇게하면 원해 index.tsx에 있는 home.getLayout에 있는 layout 그대로 스토리북에 표현되겠지. 요렇게 말이야. 👇👇👇
그럼 스토리북도 얼추 끝났네. 한 가지 해결 못한 점만 빼면 말야. 왜 사파리 스토리북에서는 조선 글씨체가 나오지? 아 킹받아. 이건 따로 해결해보자. 아니네 사파리에서는 다 이러네 아.. 킹...
추가) 사파리에서 정상적으로 폰트가 로드되지 못하는 이유는 두 가지야. 첫번째는 _app.tsx에서 해주는 cssBaseLine이 storybook에서는 없어서 그렇고 두 번째는 _document.tsx에서 구글 폰트를 import하기 때문에 storybook에서는 적용이 안되지. 게다가 사파리에는 내장 폰트도 없거든. 이 두가지 문제 모두 스토리북의 preview.js에서 처리해주면 되는데 <cssBaseLine />도 삽입하고 폰트도 로드해주자. 아래와 같이 말이지👇👇👇
//preview.js
import CssBaseline from "@mui/material/CssBaseline";
...
export const decorators = [
(Story) => (
<>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
<CssBaseline />
<Story />
</>
),
];
...
일부 소스 코드중에 font-family를 monospace로 해놓게 있는데 이것도 사파리 브라우저에 없는 폰트. 마찬가지로 사용하려면 웹폰트로 삽입해야만 하겠지? 아님 바꾸던가 난 Roboto로 변경함.
아래는 github repository 주소! 👇👇👇
https://github.com/peter-bang/nextjs-basic-configuration
이제 마지막으로 storybook-addon-material-ui 라는 걸 설치해볼게. 이건 옵션이야 해도 그만 안해도 그만. 스토리북 addon중 하나인데 업데이트가 1년 전이니까 알아서들 하자.
뭐 설명으로는 mui 컴포넌트 개발을 돕는 애라고 하는데 역시 지능이 낮은 나는 똥인지 된장인지 찍어먹어봐야 알지.
해봤는데 안되네 ㅋㅋㅋ 걍 하지 말자!
다들 안녕~
'Next.js' 카테고리의 다른 글
Next.js - 회사 홈페이지 제작 #2 (0) | 2022.07.18 |
---|---|
Next.js - 회사 홈페이지 제작 #1 (0) | 2022.07.12 |
Next.JS 프로젝트 기본 템플릿 만들기#2 (0) | 2022.06.15 |
Next.JS 프로젝트 기본 템플릿 만들기#1 (0) | 2022.06.14 |
Next.js NextUI (0) | 2022.06.08 |