테마
네트워킹 마이크로앱 구현
Tailwind CSS의 유틸리티 우선 접근법으로 스타일링하고, Recoil로 전역 상태를 관리하며, prefix 기반 스타일 격리와 유틸리티 함수 공유 패턴을 적용하여 인맥 관리 마이크로앱을 구현한다.
학습 목표
- Tailwind CSS의 prefix 설정으로 마이크로앱 간 클래스명 충돌을 방지하는 방법을 이해한다
- 유틸리티 함수(
style())를 만들어 Tailwind 클래스에 접두사를 자동 부착하는 패턴을 구현할 수 있다 - Recoil의 atom과
useRecoilState를 활용한 상태 관리를 적용할 수 있다 - Container/Presentational 패턴으로 데이터 계층과 UI 계층을 분리할 수 있다
1. Tailwind CSS 접두사 기반 스타일 격리
마이크로프론트엔드에서 Tailwind CSS를 사용할 때 가장 큰 문제는 클래스명 충돌이다. 여러 앱이 동일한 flex, p-4 같은 유틸리티 클래스를 사용하면 우선순위 충돌이 발생한다. 이를 해결하기 위해 prefix 설정을 사용한다.
javascript
// apps/network/tailwind.config.js
module.exports = {
prefix: "network--",
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {}
},
plugins: []
};prefix 적용 후 사용 예시:
html
<!-- 원본 Tailwind 클래스 -->
<div class="flex flex-col gap-4 p-4">
<!-- prefix="network--" 적용 후 -->
<div class="network--flex network--flex-col network--gap-4 network--p-4">2. 스타일 유틸리티 함수
매번 접두사를 수동으로 붙이는 것은 번거롭다. style() 유틸리티 함수로 자동화한다.
typescript
// src/styles/f.css.ts
const prefix = "network--";
export const style = (classNames: string[]): string => {
return classNames.map((className) => `${prefix}${className}`).join(" ");
};typescript
// src/components/layout.css.ts
import { style } from "../styles/f.css";
export const wrapper = style([
"flex", "flex-row", "gap-6",
"mx-auto", "my-0", "p-4", "mx-w-1128px"
]);
export const left = style([
"flex", "flex-col", "w-225px", "gap-2.5"
]);
export const center = style([
"flex", "flex-col", "w-879px", "gap-2.5"
]);typescript
// src/components/Layout.tsx
import React from "react";
import * as css from "./layout.css";
const Layout: React.FC<React.PropsWithChildren> = ({ children }) => {
return (
<div className={css.wrapper}>
<div className={css.left}>
<MyNetworkContainer />
</div>
<div className={css.center}>{children}</div>
</div>
);
};커스텀 픽셀 값 처리: Tailwind 기본 설정에 없는 구체적인 픽셀 값은 index.scss에 커스텀 클래스로 추가한다.
scss
// src/index.scss
@tailwind base;
@tailwind components;
@tailwind utilities;
.network--mx-w-1128px { max-width: 1128px; }
.network--w-225px { width: 225px; }
.network--w-879px { width: 879px; }3. Recoil을 활용한 상태 관리
네트워킹 마이크로앱은 Recoil을 사용하여 전역 상태를 관리한다. Jotai와 유사하지만 atom에 고유 key가 필요하고 RecoilRoot Provider로 감싸야 한다.
typescript
// src/atoms.ts
import { atom } from "recoil";
import type { MyNetworkType } from "./types";
export const myNetworkAtom = atom<MyNetworkType | null>({
key: "myNetwork",
default: null
});typescript
// src/containers/MyNetworkContainer.tsx
import React, { useCallback } from "react";
import { useRecoilState } from "recoil";
import { myNetworkAtom } from "../atoms";
import useAuth0Client from "../hooks/useAuth0Client";
import { getMyNetwork } from "../api";
import MyNetwork from "../components/MyNetwork";
const MyNetworkContainer: React.FC = () => {
const auth0Client = useAuth0Client();
const [myNetwork, setMyNetwork] = useRecoilState(myNetworkAtom);
const patchMyNetwork = useCallback(async () => {
try {
const token = await auth0Client.getTokenSilently();
const data = await getMyNetwork(token);
setMyNetwork(data);
} catch (e) {
alert(e);
}
}, [auth0Client, setMyNetwork]);
return (
<MyNetwork
myNetwork={myNetwork}
patchMyNetwork={patchMyNetwork}
/>
);
};
export default MyNetworkContainer;4. 인맥 관리 기능 구현
typescript
// src/types.ts
import type { User } from "@auth0/auth0-spa-js";
export interface UserType extends User {
// Auth0 기본 필드 + 확장
}
export interface MyNetworkType {
connectionCount: number;
eventCount: number;
pageCount: number;
user: UserType;
}
export interface ConnectionType {
name: string;
picture: string;
role: string;
networkCount: number;
}typescript
// src/api.ts
import type { MyNetworkType, ConnectionType } from "./types";
export async function getMyNetwork(token: string): Promise<MyNetworkType> {
const response = await fetch("http://localhost:4000/myNetwork", {
headers: { authorization: `Bearer ${token}` }
});
return await response.json();
}
export async function getConnections(token: string): Promise<ConnectionType[]> {
const response = await fetch("http://localhost:4000/connections", {
headers: { authorization: `Bearer ${token}` }
});
return await response.json();
}typescript
// src/routes.tsx (Recoil + Auth0 + Layout 통합)
export const routes: RouteObject[] = [
{
path: "/",
element: (
<Auth0ClientProvider>
<RecoilRoot>
<Layout>
<AppRoutingManager type="app-network" />
</Layout>
</RecoilRoot>
</Auth0ClientProvider>
),
errorElement: <div>app-network-error</div>,
children: [
{ index: true, element: <div>네트워크 홈</div> }
]
}
];5. 네트워킹 앱을 Shell에 연결
javascript
// apps/shell/webpack.config.js
new ModuleFederationPlugin({
name: "shell",
remotes: {
posting: "posting@http://localhost:3001/remoteEntry.js",
edu: "edu@http://localhost:3002/remoteEntry.js",
network: "network@http://localhost:3003/remoteEntry.js"
}
})스타일링 방식 비교 (3개 마이크로앱):
| 마이크로앱 | 스타일링 | 상태 관리 | 격리 방식 |
|---|---|---|---|
| Posting | CSS Modules | useState (로컬) | 빌드 타임 해시 |
| Education | Emotion | Jotai | edu-- 접두사 + 해시 |
| Network | Tailwind CSS | Recoil | network-- prefix |
핵심 정리
- Tailwind CSS의
prefix옵션으로 모든 유틸리티 클래스에network--접두사를 자동 부착하여 마이크로앱 간 스타일 충돌을 방지한다 style()유틸리티 함수는 클래스명 배열을 받아 접두사를 자동 부착하고 공백으로 연결된 문자열을 반환한다- Tailwind 기본 설정에 없는 구체적 픽셀 값은
index.scss에 커스텀 클래스로 정의한다 - Recoil은
atom({ key, default })로 상태를 정의하고,useRecoilState로 읽기/쓰기를 동시에 수행한다 injector.tsx에서import "./index.scss"를 추가해야 Shell에 임베드될 때도 Tailwind 스타일이 적용된다- 세 마이크로앱이 각기 다른 스타일링/상태 관리 도구를 사용해도 Module Federation의
shared설정으로 React를 하나만 로드한다
다음 단계
- 05-채용-서비스.md: Redux Toolkit으로 상태 관리를 구현하고, 채용 공고 목록/상세/지원 기능을 완성하며, Redux Store를 Module Federation shared로 설정한다