1. App Front 애플리케이션 개요
App Front는 앞서 만들어 본 Flight 및 Hotel 모듈을 통합하는 애플리케이션입니다. 이 애플리케이션은 각 모듈에서 제공하는 기능을 하나의 통합된 사용자 인터페이스로 제공합니다. 이 글에서는 app-front 애플리케이션을 구성하고, 각 모듈을 연동하는 방법을 단계별로 설명합니다.
2. App Front 애플리케이션 구성하기
1. `package.json` 및 주요 스크립트
`package.json` 파일은 다음과 같이 구성됩니다:
{
"name": "amu-front",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "rm -rf .next && SET NEXT_PRIVATE_LOCAL_WEBPACK=true && next dev -p 3010",
"build": "SET NEXT_PRIVATE_LOCAL_WEBPACK=true && next build"
},
"dependencies": {
"@amu/amu-ui-system": "file:../ui-system",
"axios": "^0.24.0",
"next": "^12.0.7",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"typescript": "^4.5.2",
"webpack": "^5.58.2"
}
}
- dev: 개발 서버를 실행합니다. `NEXT_PRIVATE_LOCAL_WEBPACK` 환경 변수를 설정하여 Webpack을 로컬에서 실행합니다.
- build: 빌드 명령어로, `NEXT_PRIVATE_LOCAL_WEBPACK` 환경 변수를 설정하여 빌드를 수행합니다.
2. `next.config.js` 파일 설정과 NextFederationPlugin 사용법
`next.config.js` 파일은 다음과 같이 구성됩니다:
const NextFederationPlugin = require('@module-federation/nextjs-mf');
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
compiler: {
styledComponents: true,
},
eslint: {
ignoreDuringBuilds: true,
},
webpack(config, options) {
const location = options.isServer ? 'ssr' : 'chunks';
config.plugins.push(
new NextFederationPlugin({
name: 'container',
filename: 'static/chunks/remoteEntry.js',
remotes: {
flight: `flight@http://localhost:3011/_next/static/${location}/remoteEntry.js`,
hotel: `hotel@http://localhost:3012/_next/static/${location}/remoteEntry.js`,
},
exposes: {
'./home': './src/pages/index.tsx',
'./layout': './src/components/Layout/index.tsx',
'./flightSearch': './src/components/FlightSearch/index.tsx',
},
shared: {
'styled-components': { eager: true, singleton: true },
'@tanstack/react-query': { eager: true, singleton: true },
dayjs: { eager: true, singleton: true },
axios: { singleton: true },
'@amu/amu-ui-system': { eager: true, singleton: true },
'next/image': { eager: true, singleton: true },
},
})
);
return config;
},
images: {
domains: ['*', 'cdn.domain.net'],
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.domain.net',
port: '',
},
],
formats: ['image/webp'],
dangerouslyAllowSVG: true,
},
};
module.exports = nextConfig;
NextFederationPlugin 플러그인은 모듈 연동을 위해 설정됩니다.
- name: 현재 모듈의 이름.
- filename: 원격 엔트리 파일의 이름.
- remotes: 원격 모듈의 URL 설정.
- exposes: 현재 모듈에서 제공하는 컴포넌트 경로.
- shared: 공유 모듈 설정.
3. 주요 컴포넌트 및 페이지 구성
`Layout` 컴포넌트
// src/components/Layout/index.tsx
import React from 'react';
import styled from 'styled-components';
const Layout = ({ children }) => {
return <Container>{children}</Container>;
};
const Container = styled.div`
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
`;
export default Layout;
`MetaInfo` 컴포넌트
// src/components/MetaInfo/index.tsx
import Head from 'next/head';
const DEFAULT_META = {
title: 'App Front',
description: 'An application integrating Flight and Hotel modules.',
keywords: ['flight', 'hotel', 'booking'],
};
type Props = {
title?: string;
description?: string;
keywords?: string[];
image?: string;
};
const MetaInfo = ({
title = DEFAULT_META.title,
description = DEFAULT_META.description,
keywords = DEFAULT_META.keywords,
image = 'https://img.png',
}: Props) => {
return (
<Head>
<title>{title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" />
<meta name="description" content={description} />
<meta name="keywords" content={keywords.join(', ')} />
<meta property="og:image" content={image} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content="www.allmyuniverse.com" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="ko_KR" />
</Head>
);
};
export default MetaInfo;
4. 메인 페이지 (`home`) 구성 및 동적 임포트
`Home` 페이지
// src/pages/index.tsx
import dynamic from 'next/dynamic';
import React, { useState } from 'react';
import Layout from 'components/Layout';
import MetaInfo from 'components/MetaInfo';
import { Button } from '@amu/amu-ui-system';
import styled from 'styled-components';
const FlightSearch = dynamic(() => import('flight/flightSearch'), {
ssr: false,
});
const HotelSearch = dynamic(() => import('hotel/hotelSearch'), {
ssr: false,
});
const Home = () => {
const [toggle, setToggle] = useState(false);
return (
<Layout>
<MetaInfo />
<ButtonWrapper>
<Button onClick={() => setToggle(!toggle)}>
{toggle ? 'Flight' : 'Hotel'}
</Button>
</ButtonWrapper>
{toggle ? <FlightSearch /> : <HotelSearch />}
</Layout>
);
};
const ButtonWrapper = styled.div`
margin-bottom: 20px;
`;
export default Home;
5. 글로벌 스타일과 폰트 설정
`globalStyle.ts` 파일
// src/styles/globalStyle.ts
import { createGlobalStyle } from 'styled-components';
import PoppinsR from './fonts/poppins-v19-latin-regular.woff';
import NotoSansB from './fonts/NotoSans-Bold.woff';
const GlobalStyle = createGlobalStyle`
@font-face {
font-family: 'Poppins';
src: url(${PoppinsR}) format('woff');
font-weight: 400;
}
@font-face {
font-family: 'NotoSansKR';
src: url(${NotoSansB}) format('woff');
font-weight: 700;
}
body {
font-family: 'Poppins', 'NotoSansKR', sans-serif;
}
`;
export default GlobalStyle;
6. `App` 컴포넌트 구성
`_app.tsx` 파일
// src/pages/_app.tsx
import type { AppProps } from 'next/app';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ThemeProvider } from 'styled-components';
import { theme } from '@amu/amu-ui-system';
import GlobalStyle from '../styles/globalStyle';
import MetaInfo from 'components/MetaInfo';
import dynamic from 'next/dynamic';
import React from 'react';
const poppins = Poppins({
subsets: ['latin'],
weight: ['400', '700'],
display: 'swap',
});
const notosansKr = Noto_Sans_KR({
subsets: ['latin'],
weight: ['400', '700'],
display: 'swap',
});
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: 0,
refetchOnWindowFocus: false,
},
},
});
const AlertContainer = dynamic(
() => import('@amu/amu-ui-system').then((r) => r.AlertContainer),
{ ssr: false }
);
const ConfirmContainer = dynamic(
() => import('@amu/amu-ui-system').then((r) => r.ConfirmContainer),
{ ssr: false }
);
const MobileAlertContainer = dynamic(
() => import('@amu/amu-ui-system').then((r) => r.MobileAlertContainer),
{ ssr: false }
);
const ToastContainer = dynamic(
() => import('@amu/amu-ui-system').then((r) => r.ToastContainer),
{ ssr: false }
);
export default function App({ Component, pageProps }: AppProps) {
return (
<ThemeProvider theme={theme}>
<MetaInfo />
<GlobalStyle />
<style jsx global>
{`
html {
font-family: ${poppins.style.fontFamily}, ${notosansKr.style.fontFamily};
}
${pageProps.isMobile &&
`
#__next {
display: initial !important;
}
`}
`}
</style>
<QueryClientProvider client={queryClient}>
<Component {...pageProps} />
<AlertContainer />
<ConfirmContainer />
<ToastContainer />
<MobileAlertContainer />
</QueryClientProvider>
</ThemeProvider>
);
}
3. 연동 테스트 및 최종 빌드
1. Flight 및 Hotel 모듈과의 연동 테스트 방법
통합 애플리케이션이 제대로 연동되었는지 테스트하기 위해 다음 단계를 따릅니다:
로컬 서버 실행
각 모듈의 개발 서버를 실행합니다.
cd flight
npm run dev
cd hotel
npm run dev
cd app-front
npm run dev
브라우저에서 확인
브라우저에서 `http://localhost:3010`을 열어 통합 애플리케이션을 확인합니다.
2. 개발 및 빌드 시 발생할 수 있는 문제 해결 방법
- 모듈이 제대로 로드되지 않는 경우:
- `next.config.js` 파일의 `remotes` 설정이 올바른지 확인합니다.
- 각 모듈의 개발 서버가 실행 중인지 확인합니다.
- 스타일이 적용되지 않는 경우:
- `styled-components` 설정이 올바른지 확인합니다.
- 글로벌 스타일이 적용되는지 확인합니다.
3. 최종 빌드 및 배포 과정
최종 빌드
cd app-front
npm run build
배포
- [Vercel:@b Next.js 애플리케이션을 배포하는데 널리 사용되는 플랫폼입니다. Vercel에 연결하고 프로젝트를 배포할 수 있습니다.
- [Netlify:@b 또 다른 인기 있는 배포 플랫폼으로, 설정이 간편합니다.
Vercel 배포 예시:
- Vercel 계정 생성 후 로그인.
- `Import Project` 버튼을 클릭하여 GitHub 저장소를 선택합니다.
- 배포 설정을 완료하고 `Deploy` 버튼을 클릭합니다.
Netlify 배포 예시:
- Netlify 계정 생성 후 로그인.
- `New site from Git` 버튼을 클릭하여 GitHub 저장소를 선택합니다.
- 배포 설정을 완료하고 `Deploy` 버튼을 클릭합니다.
결론
이 글에서는 app-front 애플리케이션을 구성하고, Flight 및 Hotel 모듈을 통합하는 방법을 다루었으며, 또한, 연동 테스트 방법과 빌드 및 배포 과정에 대해 알아보았습니다. NextPederationPlugin을 활용하여 모듈화된 애플리케이션을 성공적으로 구성하고 통합하면 이를 통해 더욱 효율적이고 유지보수가 용이한 프로젝트를 개발할 수 있습니다.