Skip to content

네트워킹 마이크로앱 구현

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개 마이크로앱):

마이크로앱스타일링상태 관리격리 방식
PostingCSS ModulesuseState (로컬)빌드 타임 해시
EducationEmotionJotaiedu-- 접두사 + 해시
NetworkTailwind CSSRecoilnetwork-- prefix

핵심 정리

  1. Tailwind CSS의 prefix 옵션으로 모든 유틸리티 클래스에 network-- 접두사를 자동 부착하여 마이크로앱 간 스타일 충돌을 방지한다
  2. style() 유틸리티 함수는 클래스명 배열을 받아 접두사를 자동 부착하고 공백으로 연결된 문자열을 반환한다
  3. Tailwind 기본 설정에 없는 구체적 픽셀 값은 index.scss에 커스텀 클래스로 정의한다
  4. Recoil은 atom({ key, default })로 상태를 정의하고, useRecoilState로 읽기/쓰기를 동시에 수행한다
  5. injector.tsx에서 import "./index.scss"를 추가해야 Shell에 임베드될 때도 Tailwind 스타일이 적용된다
  6. 세 마이크로앱이 각기 다른 스타일링/상태 관리 도구를 사용해도 Module Federation의 shared 설정으로 React를 하나만 로드한다

다음 단계

  • 05-채용-서비스.md: Redux Toolkit으로 상태 관리를 구현하고, 채용 공고 목록/상세/지원 기능을 완성하며, Redux Store를 Module Federation shared로 설정한다