본문 바로가기
WIKI 기술 지식 베이스

005 - 앱 테마 커스터마이징하기

원문 보기 위키 갱신

005 - 앱 테마 커스터마이징하기 (이전 프론트엔드 시스템)

이 문서는 여전히 이전 프론트엔드 시스템을 사용하는 Backstage 앱을 위한 내용이에요. 앱에서 새 프론트엔드 시스템을 사용한다면 현재 가이드를 대신 읽어보세요. Backstage는 라이트/다크 모드 변형이 있는 기본 테마와 함께 제공되며, 이 글에서는 사용자 정의 테마를 만들고 적용하는 방법을 설명드릴게요.

출처: 문서

본문

이 문서는 여전히 이전 프론트엔드 시스템을 사용하는 Backstage 앱을 위한 내용이에요. 앱에서 새 프론트엔드 시스템을 사용한다면 현재 가이드를 대신 읽어보세요.

Backstage는 라이트/다크 모드 변형이 있는 기본 테마와 함께 제공돼요. 테마는 @backstage/theme 패키지의 일부로 제공되며, 기본 테마를 커스터마이징하거나 완전히 새로운 테마를 만드는 유틸리티도 포함돼요.

사용자 정의 테마 만들기

새 테마를 만드는 가장 쉬운 방법은 @backstage/theme 패키지가 내보내는 createUnifiedTheme 함수를 사용하는 거예요. 이 함수를 사용해서 색상 팔레트와 글꼴 같은 기본 테마의 일부 기본 매개변수를 오버라이드할 수 있어요.

예를 들어 기본 라이트 테마를 기반으로 다음과 같이 새 테마를 만들 수 있어요:

packages/app/src/theme/myTheme.ts

import {  createBaseThemeOptions,  createUnifiedTheme,  palettes,} from '@backstage/theme';export const myTheme = createUnifiedTheme({  ...createBaseThemeOptions({    palette: palettes.light,  }),  fontFamily: 'Comic Sans MS',  defaultPageTheme: 'home',});

테마 파일을 배치할 packages/app/src 안에 theme 폴더를 만들어서 깔끔하게 정리하는 것을 권장해요.

@backstage/theme가 내보내는 BackstageTheme 유형과 일치하는 테마를 처음부터 만들 수도 있어요. 그 방법에 대한 자세한 내용은 Material UI 테마 문서를 참고하세요.

사용자 정의 테마 사용하기

사용자 정의 테마를 Backstage 앱에 추가하려면 테마를 createApp에 구성으로 전달해요.

예를 들어 이전 섹션에서 만든 테마를 추가하는 것은 이렇게 할 수 있어요:

packages/app/src/App.tsx

import { createApp } from '@backstage/app-defaults';import { ThemeProvider } from '@material-ui/core/styles';import CssBaseline from '@material-ui/core/CssBaseline';import LightIcon from '@material-ui/icons/WbSunny';import { UnifiedThemeProvider} from '@backstage/theme';import { myTheme } from './themes/myTheme';const app = createApp({  apis: ...,  plugins: ...,  themes: [{    id: 'my-theme',    title: 'My Custom Theme',    variant: 'light',    icon: <LightIcon />,    Provider: ({ children }) => (      <UnifiedThemeProvider theme={myTheme} children={children} />    ),  }]})

사용자 정의 테마 목록이 기본 테마를 오버라이드한다는 점에 주의하세요. 기본 테마를 계속 사용하고 싶다면 @backstage/theme에서 themes.light와 themes.dark로 내보내집니다.

사용자 정의 테마 예시

packages/app/src/theme/myTheme.ts

import {  createBaseThemeOptions,  createUnifiedTheme,  genPageTheme,  palettes,  shapes,} from '@backstage/theme';export const myTheme = createUnifiedTheme({  ...createBaseThemeOptions({    palette: {      ...palettes.light,      primary: {        main: '#343b58',      },      secondary: {        main: '#565a6e',      },      error: {        main: '#8c4351',      },      warning: {        main: '#8f5e15',      },      info: {        main: '#34548a',      },      success: {        main: '#485e30',      },      background: {        default: '#d5d6db',        paper: '#d5d6db',      },      banner: {        info: '#34548a',        error: '#8c4351',        text: '#343b58',        link: '#565a6e',      },      errorBackground: '#8c4351',      warningBackground: '#8f5e15',      infoBackground: '#343b58',      navigation: {        background: '#343b58',        indicator: '#8f5e15',        color: '#d5d6db',        selectedColor: '#ffffff',      },    },  }),  defaultPageTheme: 'home',  fontFamily: 'Comic Sans MS',  /* below drives the header colors */  pageTheme: {    home: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }),    documentation: genPageTheme({      colors: ['#8c4351', '#343b58'],      shape: shapes.wave2,    }),    tool: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.round }),    service: genPageTheme({      colors: ['#8c4351', '#343b58'],      shape: shapes.wave,    }),    website: genPageTheme({      colors: ['#8c4351', '#343b58'],      shape: shapes.wave,    }),    library: genPageTheme({      colors: ['#8c4351', '#343b58'],      shape: shapes.wave,    }),    other: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }),    app: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }),    apis: genPageTheme({ colors: ['#8c4351', '#343b58'], shape: shapes.wave }),  },});

Backstage와 Material UI 컴포넌트 오버라이드를 포함한 더 완전한 사용자 정의 테마 예시는 Backstage 데모 사이트의 Aperture 테마를 참고하세요.

사용자 정의 타이포그래피

사용자 정의 테마를 만들 때 기본 타이포그래피의 다양한 측면도 커스터마이징할 수 있어요. 단순화된 테마를 사용한 예시는 다음과 같아요:

packages/app/src/theme/myTheme.ts

import {  createBaseThemeOptions,  createUnifiedTheme,  palettes,} from '@backstage/theme';export const myTheme = createUnifiedTheme({  ...createBaseThemeOptions({    palette: palettes.light,    typography: {      htmlFontSize: 16,      fontFamily: 'Arial, sans-serif',      h1: {        fontSize: 54,        fontWeight: 700,        marginBottom: 10,      },      h2: {        fontSize: 40,        fontWeight: 700,        marginBottom: 8,      },      h3: {        fontSize: 32,        fontWeight: 700,        marginBottom: 6,      },      h4: {        fontWeight: 700,        fontSize: 28,        marginBottom: 6,      },      h5: {        fontWeight: 700,        fontSize: 24,        marginBottom: 4,      },      h6: {        fontWeight: 700,        fontSize: 20,        marginBottom: 2,      },    },    defaultPageTheme: 'home',  }),});

타이포그래피 설정의 일부만 오버라이드하고 싶다면, 예를 들어 h1만 오버라이드하려면 이렇게 하면 돼요:

packages/app/src/theme/myTheme.ts

import {  createBaseThemeOptions,  createUnifiedTheme,  defaultTypography,  palettes,} from '@backstage/theme';export const myTheme = createUnifiedTheme({  ...createBaseThemeOptions({    palette: palettes.light,    typography: {      ...defaultTypography,      htmlFontSize: 16,      fontFamily: 'Roboto, sans-serif',      h1: {        fontSize: 72,        fontWeight: 700,        marginBottom: 10,      },    },    defaultPageTheme: 'home',  }),});

사용자 정의 글꼴

사용자 정의 글꼴을 추가하려면 먼저 import할 수 있도록 글꼴을 저장해야 해요. 프론트엔드 애플리케이션의 src 폴더에 assets/fonts 디렉터리를 만드는 것을 제안해요.

그런 다음 Material UI Typography의 @font-face 구문에 따라 글꼴 스타일을 선언할 수 있어요.

그 다음 components 아래의 MuiCssBaseline의 styleOverrides를 활용해서 @font-face 배열에 글꼴을 추가할 수 있어요.

packages/app/src/theme/myTheme.ts

import MyCustomFont from '../assets/fonts/My-Custom-Font.woff2';const myCustomFont = {  fontFamily: 'My-Custom-Font',  fontStyle: 'normal',  fontDisplay: 'swap',  fontWeight: 300,  src: `    local('My-Custom-Font'),    url(${MyCustomFont}) format('woff2'),  `,};export const myTheme = createUnifiedTheme({  fontFamily: 'My-Custom-Font',  palette: palettes.light,  components: {    MuiCssBaseline: {      styleOverrides: {        '@font-face': [myCustomFont],      },    },  },});

서로 다른 글꼴이나 여러 글꼴을 사용하려면 최상위 fontFamily를 본문에 원하는 것으로 설정하고, typography의 fontFamily를 오버라이드해서 다양한 제목의 글꼴을 제어하면 돼요.

packages/app/src/theme/myTheme.ts

import MyCustomFont from '../assets/fonts/My-Custom-Font.woff2';import myAwesomeFont from '../assets/fonts/My-Awesome-Font.woff2';const myCustomFont = {  fontFamily: 'My-Custom-Font',  fontStyle: 'normal',  fontDisplay: 'swap',  fontWeight: 300,  src: `    local('My-Custom-Font'),    url(${MyCustomFont}) format('woff2'),  `,};const myAwesomeFont = {  fontFamily: 'My-Awesome-Font',  fontStyle: 'normal',  fontDisplay: 'swap',  fontWeight: 300,  src: `    local('My-Awesome-Font'),    url(${myAwesomeFont}) format('woff2'),  `,};export const myTheme = createUnifiedTheme({  fontFamily: 'My-Custom-Font',  components: {    MuiCssBaseline: {      styleOverrides: {        '@font-face': [myCustomFont, myAwesomeFont],      },    },  },  ...createBaseThemeOptions({    palette: palettes.light,    typography: {      ...defaultTypography,      htmlFontSize: 16,      fontFamily: 'My-Custom-Font',      h1: {        fontSize: 72,        fontWeight: 700,        marginBottom: 10,        fontFamily: 'My-Awesome-Font',      },    },    defaultPageTheme: 'home',  }),});

Backstage 및 Material UI 컴포넌트 스타일 오버라이드하기

사용자 정의 테마를 만들 때는 테마 객체를 사용하는 컴포넌트의 CSS 규칙에 다른 값을 적용하게 돼요. 예를 들어 Backstage 컴포넌트의 스타일은 이렇게 보일 수 있어요:

const useStyles = makeStyles<BackstageTheme>(  theme => ({    header: {      padding: theme.spacing(3),      boxShadow: '0 0 8px 3px rgba(20, 20, 20, 0.3)',      backgroundImage: theme.page.backgroundImage,    },  }),  { name: 'BackstageHeader' },);

padding이 theme.spacing에서 값을 얻는 방식을 주목하세요. 즉 사용자 정의 테마에 spacing 값을 설정하면 이 컴포넌트의 padding 속성에 영향을 주고, theme.page.backgroundImage를 사용하는 backgroundImage도 마찬가지예요. 하지만 boxShadow 속성은 테마의 어떤 값도 참조하지 않아요. 즉 사용자 정의 테마를 만드는 것만으로는 box-shadow 속성을 바꾸거나 margin 같은 아직 정의되지 않은 css 규칙을 추가하기에 충분하지 않아요. 이런 경우에는 오버라이드도 만들어야 해요.

다음과 같이 하면 돼요:

packages/app/src/theme/myTheme.ts

import {  createBaseThemeOptions,  createUnifiedTheme,  palettes,} from '@backstage/theme';export const myTheme = createUnifiedTheme({  ...createBaseThemeOptions({    palette: palettes.light,  }),  fontFamily: 'Comic Sans MS',  defaultPageTheme: 'home',  components: {    BackstageHeader: {      styleOverrides: {        header: ({ theme }) => ({          width: 'auto',          margin: '20px',          boxShadow: 'none',          borderBottom: `4px solid ${theme.palette.primary.main}`,        }),      },    },  },});

사용자 정의 로고

사용자 정의 테마 외에도, 사이트의 맨 왼쪽 위에 표시되는 로고도 커스터마이징할 수 있어요.

프론트엔드 앱에서 src/components/Root/ 폴더를 찾아요. 두 가지 컴포넌트를 찾을 수 있어요:

  • LogoFull.tsx - Sidebar 탐색이 열렸을 때 사용되는 더 큰 로고.

  • LogoIcon.tsx - Sidebar 탐색이 닫혔을 때 사용되는 더 작은 로고.

이미지를 교체하려면 해당 컴포넌트의 관련 코드를 원시 SVG 정의로 간단히 교체하면 돼요.

PNG 같은 다른 웹 이미지 형식을 import해서 사용할 수도 있어요. 이렇게 하려면 새 이미지를 src/components/Root/logo/my-company-logo.png 같은 새 하위 디렉터리에 배치하고 이 코드를 추가해요:

import MyCustomLogoFull from './logo/my-company-logo.png';const LogoFull = () => {  return <img src={MyCustomLogoFull} />;};

아이콘

지금까지 자신만의 테마를 만들고 로고를 추가하는 방법을 살펴봤어요. 다음 섹션들에서는 기존 아이콘을 오버라이드하고 아이콘을 더 추가하는 방법을 보여드릴게요.

사용자 정의 아이콘

프로젝트의 기본 아이콘도 커스터마이징할 수 있어요.

다음 아이콘들을 변경할 수 있어요.

요구 사항

  • .svg 형식의 파일

  • 아이콘을 위해 만들어진 React 컴포넌트

React 컴포넌트 만들기

프론트엔드 애플리케이션에서 src 폴더를 찾아요. assets/icons 디렉터리와 CustomIcons.tsx 파일을 만드는 것을 제안해요.

customIcons.tsx

import { SvgIcon, SvgIconProps } from '@material-ui/core';export const ExampleIcon = (props: SvgIconProps) => (  <SvgIcon {...props} viewBox="0 0 24 24">    <path      fill="currentColor"      width="1em"      height="1em"      display="inline-block"      d="M11.6335 10.8398C11.6335 11.6563 12.065 12.9922 13.0863 12.9922C14.1075 12.9922 14.539 11.6563 14.539 10.8398C14.539 10.0234 14.1075 8.6875 13.0863 8.6875C12.065 8.6875 11.6335 10.0234 11.6335 10.8398V10.8398ZM2.38419e-07 8.86719C2.38419e-07 10.1133 0.126667 11.4336 0.692709 12.5781C2.19292 15.5703 6.3175 15.5 9.27042 15.5C12.2708 15.5 16.6408 15.6055 18.2004 12.5781C18.7783 11.4453 19 10.1133 19 8.86719C19 7.23047 18.4498 5.68359 17.3573 4.42969C17.5631 3.8125 17.6621 3.16406 17.6621 2.52344C17.6621 1.68359 17.4681 1.26172 17.0842 0.5C15.291 0.5 14.1431 0.851562 12.7775 1.90625C11.6296 1.63672 10.45 1.51562 9.26646 1.51562C8.19771 1.51562 7.12104 1.62891 6.08396 1.875C4.73813 0.832031 3.59021 0.5 1.81687 0.5C1.42896 1.26172 1.23896 1.68359 1.23896 2.52344C1.23896 3.16406 1.34188 3.80078 1.54375 4.40625C0.455209 5.67188 2.38419e-07 7.23047 2.38419e-07 8.86719V8.86719ZM2.54521 10.8398C2.54521 9.125 3.60208 7.61328 5.45458 7.61328C6.20271 7.61328 6.91917 7.74609 7.67125 7.84766C8.26104 7.9375 8.85083 7.97266 9.45646 7.97266C10.0581 7.97266 10.6479 7.9375 11.2417 7.84766C11.9819 7.74609 12.7063 7.61328 13.4583 7.61328C15.3108 7.61328 16.3677 9.125 16.3677 10.8398C16.3677 14.2695 13.1852 14.7969 10.4144 14.7969H8.50646C5.72375 14.7969 2.54521 14.2734 2.54521 10.8398V10.8398ZM5.81479 8.6875C6.83604 8.6875 7.2675 10.0234 7.2675 10.8398C7.2675 11.6563 6.83604 12.9922 5.81479 12.9922C4.79354 12.9922 4.36208 11.6563 4.36208 10.8398C4.36208 10.0234 4.79354 8.6875 5.81479 8.6875Z"    />  </SvgIcon>);

사용자 정의 아이콘 사용하기

packages/app/src/App.tsx에 사용자 정의 아이콘을 공급해요.

packages/app/src/App.tsx

import { ExampleIcon } from './assets/customIcons'const app = createApp({  apis,  components: {    {/* ... */}  },  themes: [    {/* ... */}  ],  icons: {    github: ExampleIcon,  },  bindRoutes({ bind }) {    {/* ... */}  }})

아이콘 추가하기

기본 아이콘이 요구에 맞지 않다면 아이콘을 더 추가해서 엔티티의 링크 같은 다른 곳에서 사용할 수 있게 할 수 있어요. 이 예시에서는 Material UI의 아이콘, 특히 AlarmIcon을 사용할 거예요. 방법은 다음과 같아요:

  • 먼저 /packages/app/src에서 App.tsx를 열어요.

  • 그런 다음 아이콘을 import하고, 다른 import들에 이것을 추가해요: import AlarmIcon from '@material-ui/icons/Alarm';

  • 다음으로 createApp에 아이콘을 이렇게 추가해요:

packages/app/src/App.tsx

const app = createApp({  apis: ...,  plugins: ...,  icons: {    alert: AlarmIcon,  },  themes: ...,  components: ...,});
  • 이제 엔티티 링크에서 아이콘을 이렇게 alert로 참조할 수 있어요:
apiVersion: backstage.io/v1alpha1kind: Componentmetadata:  name: artist-lookup  description: Artist Lookup  links:    - url: https://example.com/alert      title: Alerts      icon: alert

결과는 이렇게 보여요:

이 아이콘을 사용하는 또 다른 방법은 AppContext에서 이렇게 사용하는 거예요:

import { useApp } from '@backstage/core-plugin-api';const app = useApp();const alertIcon = app.getSystemIcon('alert');

여러 위치에서 사용하려는 아이콘이 있다면 이 방법을 사용하고 싶을 수 있어요.

아이콘이 기본 아이콘이나 추가한 아이콘 중 하나로 사용할 수 없으면 Material UI의 LanguageIcon으로 대체돼요.

사용자 정의 사이드바

지금까지 Backstage 앱을 커스터마이징할 수 있는 많은 방법을 살펴봤어요. 다음 섹션에서는 사이드바를 커스터마이징하는 방법을 보여드릴게요.

사이드바 하위 메뉴

이 예시에서는 사이드바를 하위 메뉴로 확장하는 방법을 보여드릴게요:

  • 사이드바 코드가 있는 packages/app/src/components/Root에 위치한 Root.tsx 파일을 열어요.

  • 그런 다음 useApp에 대해 다음 import를 추가해요:

packages/app/src/components/Root/Root.tsx

import { useApp } from '@backstage/core-plugin-api';
  • 그런 다음 @backstage/core-components import를 이렇게 업데이트해요:

packages/app/src/components/Root/Root.tsx

import {  Sidebar,  sidebarConfig,  SidebarDivider,  SidebarGroup,  SidebarItem,  SidebarPage,  SidebarScrollWrapper,  SidebarSpace,  useSidebarOpenState,  Link,  GroupIcon,  SidebarSubmenu,  SidebarSubmenuItem,} from '@backstage/core-components';
  • 마지막으로 <SidebarItem icon={HomeIcon} to="catalog" text="Home" />를 이것으로 교체해요:

packages/app/src/components/Root/Root.tsx

<SidebarItem icon={HomeIcon} to="catalog" text="Home">  <SidebarSubmenu title="Catalog">    <SidebarSubmenuItem      title="Domains"      to="catalog?filters[kind]=domain"      icon={useApp().getSystemIcon('kind:domain')}    />    <SidebarSubmenuItem      title="Systems"      to="catalog?filters[kind]=system"      icon={useApp().getSystemIcon('kind:system')}    />    <SidebarSubmenuItem      title="Components"      to="catalog?filters[kind]=component"      icon={useApp().getSystemIcon('kind:component')}    />    <SidebarSubmenuItem      title="APIs"      to="catalog?filters[kind]=api"      icon={useApp().getSystemIcon('kind:api')}    />    <SidebarDivider />    <SidebarSubmenuItem      title="Resources"      to="catalog?filters[kind]=resource"      icon={useApp().getSystemIcon('kind:resource')}    />    <SidebarDivider />    <SidebarSubmenuItem      title="Groups"      to="catalog?filters[kind]=group"      icon={useApp().getSystemIcon('kind:group')}    />    <SidebarSubmenuItem      title="Users"      to="catalog?filters[kind]=user"      icon={useApp().getSystemIcon('kind:user')}    />  </SidebarSubmenu></SidebarItem>

Backstage 앱을 시작하고 사이드바의 Home 옵션 위에 마우스를 올리면 카탈로그의 다양한 Kind로 연결되는 멋진 하위 메뉴가 나타나는 것을 볼 수 있을 거예요. 이런 모습이 될 거예요:

이것을 사용하는 더 많은 방법은 Storybook Sidebar 예시에서 볼 수 있어요.

사용자 정의 홈페이지

사용자 정의 테마, 사용자 정의 로고 외에도 앱의 홈페이지를 커스터마이징할 수 있어요. 전체 가이드는 다음 페이지에서 읽을 수 있어요.

Material UI v5로 마이그레이션하기

이제 Backstage에서 Material UI v5를 지원해요. 시작하려면 마이그레이션 가이드를 확인해 보세요.

더 알아보기 (Learn more)