UI5

UI5 로딩 느려지는 실수 5가지 #shorts #SAP #UI5

📖 개요: 이 글에서 다루는 것

UI5 앱을 처음 열었을 때 흰 화면이 3~5초씩 이어진다면, 대부분은 서버 성능이 아니라 부트스트랩과 리소스 로딩 방식의 문제입니다. 이 실전 예제에서는 SAPUI5/OpenUI5(1.120 LTS 기준) 앱의 초기 로딩 속도를 개선하는 7가지 방법을 부트스트랩 → 번들링 → 데이터 로딩 순서로 단계별로 적용합니다.

  • 비동기 부트스트랩과 manifest-first 로딩 원리 이해
  • Component-preload.js 번들 생성으로 요청 수 90% 이상 줄이기
  • OData earlyRequests·라우팅 지연 로딩 등 실무 시나리오 적용
  • 프로덕션 빌드·캐시 전략·성능 측정까지 체크리스트로 검증

📚 미리 알아두면 좋은 배경

MVC 구조(View/Controller/Component)로 UI5 앱을 한 번이라도 만들어본 경험이 있다면 충분합니다. manifest.json의 역할, OData 모델의 기본 개념, 그리고 브라우저 개발자 도구의 Network 탭을 읽을 수 있으면 각 최적화가 왜 효과가 있는지 직접 확인하며 따라올 수 있습니다.

🔧 환경과 준비물

이 글의 코드는 다음 환경에서 검증하는 것을 권장합니다.

항목버전/설정
SAPUI5 / OpenUI51.120 LTS 이상 (1.71 이상이면 대부분 동일 적용 가능)
UI5 Tooling@ui5/cli v3 이상 (specVersion 3.0)
Node.js18 LTS 이상
배포 대상SAP BTP Cloud Foundry(HTML5 Repository) 또는 온프레미스 Fiori Launchpad

측정 도구는 Chrome DevTools의 Network/Performance 탭과 UI5 진단 도구(Ctrl+Shift+Alt+S로 여는 Support Assistant)를 사용합니다. 최적화는 반드시 "측정 → 적용 → 재측정" 순서로 진행하는 것이 일반적입니다.

💡 핵심 개념: 로딩이 느린 진짜 이유

UI5 앱의 초기 로딩을 공항 수속에 비유하면 이해가 쉽습니다. 승객(리소스 파일)이 한 명씩 순서대로 수속(HTTP 요청)을 밟으면 줄이 한없이 길어지고, 단체 수속(번들링)과 사전 온라인 체크인(preload·캐싱)을 쓰면 같은 인원도 몇 분 만에 통과합니다. 느린 UI5 앱은 대부분 다음 세 구간에서 시간을 잃습니다.

  1. 부트스트랩 구간sap-ui-core.js가 동기(sync) 모드로 로딩되면 코어가 다 내려올 때까지 브라우저 렌더링이 통째로 멈춥니다. data-sap-ui-async="true"를 켜면 코어·라이브러리·컴포넌트가 병렬로 내려오면서 메인 스레드 블로킹이 사라집니다.
  2. 리소스 로딩 구간 — 개발 모드에서는 View.xml, Controller.js, i18n이 각각 개별 요청으로 내려옵니다. 파일이 60개면 요청도 60번입니다. 빌드 시 Component-preload.js 하나로 묶으면 요청 수가 극적으로 줄어듭니다. 여기에 manifest-first(컴포넌트 코드보다 manifest.json을 먼저 받아 의존 라이브러리를 미리 병렬 로딩)까지 더하면 워터폴이 짧아집니다.
  3. 데이터 로딩 구간 — OData 서비스의 $metadata 문서는 수백 KB에 달하는 경우가 많습니다. V4 모델의 earlyRequests, V2 모델의 metadataUrlParams와 캐시 토큰을 활용하면 메타데이터를 렌더링과 병렬로, 또는 브라우저 캐시에서 가져올 수 있습니다.

정리하면 이 글의 7가지 방법은 다음과 같습니다. (1) 비동기 부트스트랩, (2) manifest-first, (3) 필요한 라이브러리만 선언, (4) Component-preload 번들, (5) OData 메타데이터 최적화, (6) 라우팅 기반 지연 로딩, (7) 캐시버스터·CDN 활용. 아래 3단계 코드에서 순서대로 적용합니다.

💻 실전 코드 3단계

1단계 — 기본: 비동기 부트스트랩과 manifest-first (방법 1~3)

영업 조직에서 쓰는 판매오더 조회 앱 sales.order.monitor를 예로 듭니다. 가장 먼저 index.html의 부트스트랩 태그를 점검합니다.

<script id="sap-ui-bootstrap"
    src="resources/sap-ui-core.js"
    data-sap-ui-theme="sap_horizon"
    data-sap-ui-async="true"
    data-sap-ui-compat-version="edge"
    data-sap-ui-on-init="module:sales/order/monitor/index"
    data-sap-ui-resource-roots='{ "sales.order.monitor": "./" }'>
</script>

포인트는 세 가지입니다. data-sap-ui-async="true"로 코어와 라이브러리를 병렬 로딩하고, data-sap-ui-libs는 아예 선언하지 않거나 첫 화면에 꼭 필요한 것만 넣습니다(나머지는 manifest가 처리). 초기화 코드는 인라인 스크립트 대신 모듈로 분리해 CSP 위반 없이 로딩합니다.

// index.js — ComponentSupport 대신 명시적 생성 예
sap.ui.define([
    "sap/ui/core/ComponentContainer"
], function (ComponentContainer) {
    "use strict";
    new ComponentContainer({
        name: "sales.order.monitor",
        manifest: true,        // manifest-first: 컴포넌트 코드보다 manifest 먼저
        async: true,
        height: "100%"
    }).placeAt("content");
});

manifest: true가 켜지면 프레임워크가 manifest.json을 먼저 내려받아 sap.ui5/dependencies/libs에 선언된 라이브러리 preload를 컴포넌트 코드와 병렬로 요청합니다. 그래서 manifest에는 실제 사용하는 라이브러리만 남기는 것이 중요합니다.

{
  "sap.ui5": {
    "dependencies": {
      "minUI5Version": "1.120.0",
      "libs": {
        "sap.m": {},
        "sap.f": {},
        "sap.ui.comp": { "lazy": true }
      }
    },
    "rootView": {
      "viewName": "sales.order.monitor.view.App",
      "type": "XML",
      "async": true,
      "id": "app"
    }
  }
}

스마트 컨트롤처럼 특정 화면에서만 쓰는 라이브러리는 "lazy": true로 표시하면 초기 로딩에서 제외되고 실제 사용 시점에 로딩됩니다.

2단계 — 실무 시나리오: OData 최적화와 지연 로딩, 그리고 측정 (방법 5~6)

판매오더 앱은 목록 화면이 먼저 뜨고 상세 화면은 클릭해야 열립니다. 그런데 개발 모드 워터폴을 보면 상세 화면의 View/Controller까지 초기에 내려오는 경우가 있습니다. 라우팅의 지연 로딩과 OData V4 earlyRequests를 함께 적용합니다.

{
  "sap.ui5": {
    "models": {
      "": {
        "dataSource": "salesOrderService",
        "settings": {
          "earlyRequests": true,
          "autoExpandSelect": true,
          "operationMode": "Server"
        }
      }
    },
    "routing": {
      "config": { "async": true, "viewType": "XML",
                  "path": "sales.order.monitor.view", "controlId": "app" },
      "routes": [
        { "name": "orderList",   "pattern": "",                 "target": "orderList" },
        { "name": "orderDetail", "pattern": "orders/{orderId}", "target": "orderDetail" }
      ],
      "targets": {
        "orderList":   { "id": "orderList",   "name": "OrderList" },
        "orderDetail": { "id": "orderDetail", "name": "OrderDetail" }
      }
    }
  }
}

earlyRequests: true는 $metadata와 보안 토큰 요청을 모델 생성 즉시 발사해, 첫 목록 바인딩이 메타데이터를 기다리지 않게 합니다. 라우팅 타깃은 해당 라우트에 진입할 때 View를 비동기 로딩하므로, 상세 화면 리소스가 초기 번들 경로에서 자연스럽게 분리됩니다. 효과는 반드시 측정으로 확인합니다.

// Component.js — 초기 로딩 구간 측정과 에러 로깅
sap.ui.define([
    "sap/ui/core/UIComponent",
    "sap/base/Log",
    "sap/ui/performance/Measurement"
], function (UIComponent, Log, Measurement) {
    "use strict";
    return UIComponent.extend("sales.order.monitor.Component", {
        metadata: { manifest: "json", interfaces: ["sap.ui.core.IAsyncContentCreation"] },

        init: function () {
            Measurement.start("som-init", "Component init to first render");
            UIComponent.prototype.init.apply(this, arguments);
            this.getRouter().initialize();

            // OData 요청 실패를 초기 단계에서 잡아 사용자에게 알림
            this.getModel().attachDataReceived
                ? null
                : this.getModel().attachEvent("dataRequested", function () {
                      Log.info("OData request started", null, "som.perf");
                  });

            this.getRootControl().attachAfterRendering(function () {
                Measurement.end("som-init");
                var oResult = Measurement.getMeasurement("som-init");
                Log.info("Initial render: " + Math.round(oResult.duration) + "ms",
                         null, "som.perf");
            });
        }
    });
});

IAsyncContentCreation 인터페이스를 선언하면 rootView와 중첩 뷰 생성이 전부 비동기로 강제되어, 실수로 동기 팩토리를 호출하는 코드가 섞여도 에러로 조기에 드러납니다. 측정값은 Log.setLevel로 개발 환경에서만 출력되게 조절하는 방식이 일반적입니다.

3단계 — 프로덕션: 번들 빌드와 캐시 전략 (방법 4, 7)

개발 서버에서는 빠져 있던 결정타가 번들입니다. UI5 Tooling으로 Component-preload.js를 생성하면 View·Controller·i18n·manifest가 하나의 압축 파일로 묶입니다.

# ui5.yaml
specVersion: "3.0"
metadata:
  name: sales.order.monitor
type: application
builder:
  bundles:
    - bundleDefinition:
        name: "sales/order/monitor/Component-preload.js"
        sections:
          - mode: preload
            filters:
              - "sales/order/monitor/"
              - "!sales/order/monitor/test/"
              - "!sales/order/monitor/view/OrderDetail*"  # 상세 화면은 지연 로딩 유지
            resolve: false
            sort: true
      bundleOptions:
        optimize: true
// 빌드 및 결과 확인
// npx ui5 build --all --clean-dest
// dist/ 폴더에 Component-preload.js 생성 → Network 탭에서 요청 수 비교

빌드 후 Network 탭을 비교하면 개발 모드에서 50~70건이던 앱 리소스 요청이 2~3건으로 줄어드는 것을 확인할 수 있습니다. 배포 단계에서는 캐시 전략이 남습니다.

  • 캐시버스터 — Fiori Launchpad/ABAP 환경이라면 /sap/bc/ui5_ui5/.../~cachebuster~/ 경로 토큰으로, BTP HTML5 Repository라면 빌드마다 부여되는 버전 경로로 리소스를 장기 캐시(Cache-Control: max-age)해도 배포 즉시 갱신됩니다.
  • 프레임워크 CDNsap-ui-core.js를 앱과 함께 배포하지 말고 SAP가 제공하는 CDN(예: https://ui5.sap.com/1.120.x/resources/...) 또는 런타임 공유 리소스를 참조하면, 여러 앱 사이에서 브라우저 캐시가 재사용됩니다. 버전은 마이너까지 고정하는 것을 권장합니다.
  • 보안 점검 — 인라인 스크립트를 모듈로 분리했으므로 CSP를 script-src 'self' https://ui5.sap.com 수준으로 좁힐 수 있습니다. 캐시된 번들에 민감 데이터가 포함되지 않도록 i18n·설정 파일도 함께 검토합니다.
  • 회귀 테스트 — 빌드 산출물 기준으로 OPA5/wdi5 시나리오를 1회 돌려 preload 번들에서 모듈 경로 누락이 없는지 확인하고, Lighthouse로 FCP/TTI 수치를 배포 전후 비교해 기록합니다.

⚠️ 흔한 실수와 트러블슈팅

최적화 작업 중 가장 자주 만나는 문제 세 가지를 FAQ로 정리했습니다.

Q1. async를 켰더니 "sap.ui.getCore().byId(...) undefined" 류의 오류가 납니다.

A. 동기 로딩 시절에는 부트스트랩 직후 모든 것이 준비돼 있었지만, 비동기에서는 순서가 보장되지 않습니다. 초기화 코드를 data-sap-ui-on-init="module:..." 모듈이나 Core.ready() 콜백 안으로 옮기고, 전역 sap.ui.xmlview() 같은 동기 팩토리는 XMLView.create()로 교체해야 합니다.

Q2. Component-preload.js를 만들었는데 개별 파일 요청이 여전히 보입니다.

A. 흔한 원인은 세 가지입니다. (1) dist가 아닌 소스 폴더를 배포한 경우, (2) 네임스페이스와 폴더 경로가 달라 번들 내부 모듈 키가 요청 경로와 불일치하는 경우, (3) jQuery.sap.require나 문자열 동적 경로처럼 빌드가 추적 못 하는 로딩 코드가 남은 경우입니다. Network 탭에서 404 나는 모듈 이름을 번들 파일 내부에서 검색해 키가 있는지 확인하면 원인이 빠르게 좁혀집니다.

Q3. earlyRequests를 켰는데 체감 개선이 없습니다.

A. $metadata 자체가 수 MB인 대형 서비스라면 병렬화만으로는 부족합니다. 게이트웨이 캐시 상태, 어노테이션 파일 분리 여부, 그리고 첫 화면 바인딩의 $select/autoExpandSelect로 실데이터 페이로드를 줄였는지 함께 점검해야 합니다. 병목이 프런트인지 백엔드인지는 DevTools의 TTFB로 구분합니다.

추가로, lazy: true로 미룬 라이브러리를 초기 View에서 바로 사용하면 오히려 렌더링 중간에 동기 로딩이 발생할 수 있으니, lazy 처리는 반드시 라우팅 지연 로딩과 짝지어 적용해야 합니다.

🚀 여기서 더 나아가기

초기 로딩을 잡았다면 다음 주제로 확장할 수 있습니다. 첫째, 런타임 성능 — 목록의 growing 설정, sap.ui.table 가상 스크롤, 바인딩 최적화로 상호작용 지연을 줄이는 영역입니다. 둘째, TypeScript 기반 UI5 프로젝트에서 동일한 번들 전략을 적용하는 방법. 셋째, Fiori Elements 앱에서는 이 글의 상당 부분이 프레임워크에 내장돼 있으므로, freestyle과의 차이를 비교해 보는 것도 좋은 다음 학습 주제입니다.

📚 더 읽어볼 문서

댓글 0

아직 댓글이 없습니다.