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

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

원문 보기 위키 갱신

Backstage는 라이트/다크 모드 변형이 있는 기본 테마와 함께 제공돼요. 테마는 @backstage/theme 패키지의 일부로 제공되며, 기본 테마를 커스터마이징하거나 완전히 새로운 테마를 만드는 유틸리티도 포함돼요. 이 글에서는 사용자 정의 테마를 만들고 적용하는 방법을 설명드릴게요.

출처: 문서

본문

이 문서는 새 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/plugin-app-react의 ThemeBlueprint를 사용해서 확장(extension)으로 설치돼요. 테마 확장은 프론트엔드 모듈로 묶인 다음 createApp에 전달돼요.

먼저 필요한 패키지를 설치해요:

Backstage 루트 디렉터리에서

yarn --cwd packages/app add @backstage/frontend-plugin-api @backstage/plugin-app-react

그런 다음 테마 확장을 만들고 앱에 설치해요:

packages/app/src/App.tsx

import { createApp } from '@backstage/frontend-defaults';import { createFrontendModule } from '@backstage/frontend-plugin-api';import { ThemeBlueprint } from '@backstage/plugin-app-react';import { UnifiedThemeProvider } from '@backstage/theme';import LightIcon from '@material-ui/icons/WbSunny';import { myTheme } from './theme/myTheme';const myThemeExtension = ThemeBlueprint.make({  name: 'my-theme',  params: {    theme: {      id: 'my-theme',      title: 'My Custom Theme',      variant: 'light',      icon: <LightIcon />,      Provider: ({ children }) => (        <UnifiedThemeProvider theme={myTheme} children={children} />      ),    },  },});const app = createApp({  features: [    createFrontendModule({      pluginId: 'app',      extensions: [myThemeExtension],    }),  ],});export default app.createRoot();

여러분의 사용자 정의 테마 확장은 내장 라이트/다크 테마와 나란히 추가돼요. 사용자 정의 테마가 기본 테마를 대체하도록 하려면 app-config.yaml에서 기본 테마를 비활성화할 수 있어요:

app-config.yaml

app:  extensions:    - theme:app/light: false    - theme:app/dark: false

사용자 정의 테마 예시

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} />;};

아이콘

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

사용자 정의 아이콘

@backstage/plugin-app-react의 IconBundleBlueprint를 사용해서 앱의 기본 아이콘을 커스터마이징할 수 있어요. 이것은 내장 아이콘을 오버라이드하는 확장을 만들어요.

packages/app/src/App.tsx

import { createApp } from '@backstage/frontend-defaults';import { createFrontendModule } from '@backstage/frontend-plugin-api';import { IconBundleBlueprint } from '@backstage/plugin-app-react';import { ExampleIcon } from './assets/customIcons';const customIconBundle = IconBundleBlueprint.make({  name: 'custom-icons',  params: {    icons: {      github: ExampleIcon,    },  },});const app = createApp({  features: [    createFrontendModule({      pluginId: 'app',      extensions: [customIconBundle],    }),  ],});export default app.createRoot();

아이콘 추가하기

엔티티 링크 같은 다른 곳에서 사용할 수 있도록 추가 아이콘을 등록할 수 있어요. 예를 들어 alert 아이콘을 추가하려면:

packages/app/src/App.tsx

import { createApp } from '@backstage/frontend-defaults';import { createFrontendModule } from '@backstage/frontend-plugin-api';import AlarmIcon from '@material-ui/icons/Alarm';import { IconBundleBlueprint } from '@backstage/plugin-app-react';const extraIcons = IconBundleBlueprint.make({  name: 'extra-icons',  params: {    icons: {      alert: AlarmIcon,    },  },});const app = createApp({  features: [    createFrontendModule({      pluginId: 'app',      extensions: [extraIcons],    }),  ],});export default app.createRoot();

그런 다음 엔티티 링크에서 아이콘을 이렇게 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으로 대체돼요.

사용자 정의 사이드바

새 프론트엔드 시스템에서 사이드바는 내장 app/nav 확장이 관리해요. NavContentBlueprint 확장을 만들어서 커스터마이징할 수 있어요. 하위 메뉴와 사용자 정의 그룹화가 있는 사용자 정의 사이드바 레이아웃을 만드는 자세한 지침은 사이드바 커스터마이징 문서를 참고하세요.

사용자 정의 홈페이지

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

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

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

더 알아보기 (Learn more)