SAPUI5 Routing & Navigation 완전 가이드 — Router, Target, manifest.json 설정부터 딥링크까지
SAPUI5 Routing & Navigation 완전 가이드 — Router, Target, manifest.json 설정부터 딥링크까지
1. 개요 및 라우팅의 필요성
SAPUI5 애플리케이션은 SPA(Single Page Application) 구조로 동작합니다. 페이지 전체를 새로 불러오지 않고 하나의 HTML 페이지 안에서 뷰를 교체하는 방식이기 때문에, URL과 화면 상태를 연결해 주는 라우팅(Routing) 메커니즘이 필수적입니다. 라우팅이 없으면 브라우저의 뒤로가기 버튼이 작동하지 않고, 특정 화면을 URL로 직접 공유(딥링크)할 수도 없습니다.
SAPUI5의 라우팅 시스템은 URL 해시(#) 변화를 감지하여 해당하는 Target(뷰)을 자동으로 로드합니다. manifest.json에 선언적으로 경로를 정의하고, Router가 런타임에 해시 변경을 처리하는 구조입니다.
이 가이드를 통해 다음을 학습할 수 있습니다.
- manifest.json에서 routing 섹션을 구성하는 방법
- Router 초기화와 navTo를 이용한 프로그래밍 방식 화면 이동
- Route Matched 이벤트로 파라미터를 수신하고 데이터를 바인딩하는 패턴
- History API를 활용한 뒤로가기 및 딥링크 처리
- 마스터-디테일 패턴의 중첩 라우팅 구현
- Not Found 처리, 보안, 성능 등 실무 팁
2. manifest.json routing 섹션 완전 분석
SAPUI5 라우팅의 핵심은 manifest.json의 sap.ui5.routing 섹션입니다. 이 섹션은 크게 config, routes, targets 세 부분으로 구성됩니다.
config — 전역 라우팅 설정
모든 라우트와 타겟에 공통으로 적용되는 기본값을 정의합니다.
- routerClass: 사용할 라우터 클래스. 모바일 앱이라면
sap.m.routing.Router를 권장합니다. - type / viewType: 타겟이 로드할 아티팩트 유형. 일반적으로
"View"와"XML"을 사용합니다. - path: 뷰 파일의 기본 네임스페이스 경로 (예:
"com.example.app.view"). - controlId / controlAggregation: 뷰가 삽입될 컨테이너 컨트롤의 ID와 애그리게이션.
App컨트롤의pages애그리게이션이 대표적입니다. - transition: 화면 전환 애니메이션.
"slide","show","fade"등을 지정할 수 있습니다. - bypassed.target: 어떤 라우트에도 매칭되지 않을 때 표시할 타겟(Not Found 페이지).
routes — 경로 배열
각 라우트는 URL 패턴과 타겟을 연결합니다. pattern이 빈 문자열("")이면 기본 홈 화면을 의미합니다. 패턴 내 {paramName}은 필수 파라미터, :paramName:은 선택(optional) 파라미터입니다.
targets — 뷰 매핑
타겟은 실제로 어떤 뷰를 로드할지 정의합니다. name은 뷰 파일명, level은 화면 깊이를 나타내어 전환 방향을 결정합니다(숫자가 클수록 하위 화면).
아래는 홈, 제품 목록, 제품 상세, Not Found를 포함한 전체 routing 설정 예시입니다.
{
"sap.ui5": {
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"type": "View",
"viewType": "XML",
"path": "com.example.app.view",
"controlId": "app",
"controlAggregation": "pages",
"transition": "slide",
"bypassed": {
"target": "notFound"
}
},
"routes": [
{
"pattern": "",
"name": "home",
"target": "home"
},
{
"pattern": "product/{productId}",
"name": "product",
"target": "product"
},
{
"pattern": "product/{productId}/:tab:",
"name": "productDetail",
"target": "productDetail"
}
],
"targets": {
"home": {
"id": "home",
"name": "Home",
"level": 0
},
"product": {
"id": "product",
"name": "Product",
"level": 1
},
"productDetail": {
"id": "productDetail",
"name": "ProductDetail",
"level": 2
},
"notFound": {
"id": "notFound",
"name": "NotFound"
}
}
}
}
}
위 설정에서 product/{productId} 패턴은 #/product/123 형태의 URL에 매칭됩니다. :tab:은 선택 파라미터이므로 #/product/123/info처럼 있어도 되고 없어도 됩니다.
3. Router 초기화 — Component.js 패턴
SAPUI5에서 라우터는 Component.js의 init 메서드에서 초기화합니다. metadata에 manifest: "json"을 선언하면 manifest.json의 routing 설정을 자동으로 읽어들입니다. 그 후 this.getRouter().initialize()를 호출하면 라우터가 해시 체인저(HashChanger)에 연결되어 URL 변화를 감지하기 시작합니다.
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/Device",
"./model/models"
], function (UIComponent, Device, models) {
"use strict";
return UIComponent.extend("com.example.app.Component", {
metadata: {
manifest: "json"
},
init: function () {
// 부모 클래스의 init을 먼저 호출
UIComponent.prototype.init.apply(this, arguments);
// 디바이스 모델 설정
this.setModel(models.createDeviceModel(), "device");
// Router 초기화 — manifest.json의 routing 설정을 기반으로 동작 시작
this.getRouter().initialize();
}
});
});
initialize() 메서드는 선택적으로 bIgnoreInitialHash 파라미터를 받습니다. 기본값은 false이며, 이 경우 페이지 최초 로드 시 현재 URL 해시를 파싱하여 해당 라우트를 즉시 실행합니다. 이것이 딥링크가 자동으로 작동하는 원리입니다.
4. navTo로 화면 이동하기
프로그래밍 방식으로 화면을 전환할 때는 Router의 navTo 메서드를 사용합니다. 첫 번째 인자는 라우트 이름, 두 번째는 파라미터 객체입니다.
// 기본 네비게이션 — 필수 파라미터
onProductPress: function (oEvent) {
var sProductId = oEvent.getSource()
.getBindingContext()
.getProperty("ProductID");
this.getOwnerComponent().getRouter().navTo("product", {
productId: sProductId
});
},
// 선택 파라미터 포함 네비게이션
onProductTabPress: function (sProductId, sTab) {
this.getOwnerComponent().getRouter().navTo("productDetail", {
productId: sProductId,
tab: sTab // 선택 파라미터 — 생략 가능
});
},
// replace 모드 — 히스토리에 새 항목을 추가하지 않고 현재 항목을 교체
onRedirect: function () {
this.getOwnerComponent().getRouter().navTo("home", {}, true);
// 세 번째 인자 true = bReplace
}
navTo의 시그니처는 navTo(sName, oParameters, oComponentTargetInfo, bReplace)입니다. 파라미터 값에 포함된 특수 문자(; , / ? : @ & = + $ 등)는 자동으로 URI 인코딩됩니다. navTo는 체이닝을 지원하여 this를 반환합니다.
주의: 필수 파라미터를 누락하면 라우트 패턴이 완성되지 않아 네비게이션이 실패합니다. 콘솔에 별도 에러가 출력되지 않는 경우도 있으므로, navTo 호출 전 파라미터 값이 유효한지 반드시 확인하세요.
5. Route Matched 이벤트 처리 — attachPatternMatched
라우트가 매칭되었을 때 실행할 로직은 attachPatternMatched로 등록합니다. 일반적으로 타겟 뷰의 컨트롤러 onInit에서 이벤트 핸들러를 바인딩합니다.
sap.ui.define([
"sap/ui/core/mvc/Controller"
], function (Controller) {
"use strict";
return Controller.extend("com.example.app.controller.Product", {
onInit: function () {
var oRouter = this.getOwnerComponent().getRouter();
// 이 뷰에 해당하는 라우트에만 이벤트 등록
oRouter.getRoute("product")
.attachPatternMatched(this._onRouteMatched, this);
},
_onRouteMatched: function (oEvent) {
var sProductId = oEvent.getParameter("arguments").productId;
// OData 엔티티 바인딩
this.getView().bindElement({
path: "/Products('" + sProductId + "')",
model: "catalog",
events: {
dataRequested: function () {
// 로딩 표시
},
dataReceived: function (oData) {
// 데이터가 없으면 Not Found로 이동
if (!oData.getParameter("data")) {
this.getOwnerComponent().getRouter()
.getTargets().display("notFound");
}
}.bind(this)
}
});
}
});
});
attachPatternMatched와 attachRouteMatched의 차이를 이해하는 것이 중요합니다.
- attachPatternMatched: 해당 라우트의 패턴이 직접 매칭되었을 때만 실행됩니다.
- attachRouteMatched: 해당 라우트가 다른 라우트의 부모로 매칭되어도 실행됩니다. 중첩 라우팅에서 유용합니다.
대부분의 경우 attachPatternMatched를 사용하는 것이 권장됩니다. 불필요한 중복 실행을 방지할 수 있기 때문입니다.
6. History API로 뒤로가기 구현 — 딥링크 포함
SPA에서 뒤로가기 버튼을 구현할 때, 단순히 window.history.go(-1)만 사용하면 딥링크로 직접 진입한 경우 앱 바깥으로 나가버리는 문제가 발생합니다. 이를 해결하기 위해 SAPUI5의 History 유틸리티를 활용합니다.
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/routing/History"
], function (Controller, History) {
"use strict";
return Controller.extend("com.example.app.controller.Product", {
onNavBack: function () {
var oHistory = History.getInstance();
var sPreviousHash = oHistory.getPreviousHash();
if (sPreviousHash !== undefined) {
// 이전 해시가 있으면 브라우저 히스토리로 뒤로가기
window.history.go(-1);
} else {
// 딥링크로 직접 진입한 경우 — 홈으로 이동 (replace 모드)
this.getOwnerComponent().getRouter().navTo("home", {}, true);
}
}
});
});
핵심 로직은 getPreviousHash()의 반환값입니다. 앱 내에서 네비게이션한 이력이 있으면 이전 해시 값이 존재하고, 외부에서 URL을 직접 입력하여 진입한 경우에는 undefined를 반환합니다. 이 분기를 통해 딥링크 시나리오를 안전하게 처리할 수 있습니다.
navTo의 세 번째 인자를 true로 설정하면 replace 모드로 동작하여 히스토리 스택에 새 항목을 추가하지 않습니다. 홈으로 이동 후 다시 뒤로가기를 누르면 앱 바깥으로 나가게 되므로, 이것이 일반적으로 기대하는 동작입니다.
7. 중첩 라우팅 및 마스터-디테일 패턴
SAP Fiori의 대표적인 UX 패턴인 마스터-디테일(Master-Detail)은 화면을 좌우로 분할하여 좌측에 목록, 우측에 상세 내용을 동시에 표시합니다. 이를 구현하려면 하나의 라우트에 다중 타겟을 지정합니다.
{
"routes": [
{
"pattern": "",
"name": "master",
"target": ["master", "defaultDetail"]
},
{
"pattern": "order/{orderId}",
"name": "orderDetail",
"target": ["master", "orderDetail"]
}
],
"targets": {
"master": {
"name": "Master",
"controlAggregation": "masterPages",
"controlId": "splitContainer"
},
"defaultDetail": {
"name": "DefaultDetail",
"controlAggregation": "detailPages",
"controlId": "splitContainer"
},
"orderDetail": {
"name": "OrderDetail",
"controlAggregation": "detailPages",
"controlId": "splitContainer"
}
}
}
위 설정에서 target을 배열로 지정하면 두 개의 타겟이 동시에 로드됩니다. SplitContainer의 masterPages와 detailPages 애그리게이션에 각각 뷰가 배치됩니다. 라우트가 변경되어도 master 타겟은 동일하므로 좌측 목록은 유지되고 우측 상세만 교체됩니다.
모바일 환경에서는 SplitContainer가 자동으로 단일 컬럼 모드로 전환되므로, 별도의 반응형 처리 코드가 필요 없는 것이 장점입니다.
8. 실무 팁 및 주의사항
Not Found (404) 처리
manifest.json의 config.bypassed.target에 Not Found 타겟을 지정하면, 등록된 어떤 패턴에도 매칭되지 않는 URL 접근 시 자동으로 해당 뷰가 표시됩니다. 추가로, OData 요청 후 데이터가 없는 경우에도 프로그래밍 방식으로 getTargets().display("notFound")를 호출하여 처리하는 것이 좋습니다.
파라미터 검증 및 보안
- URL 파라미터는 사용자가 임의로 조작할 수 있으므로,
_onRouteMatched에서 반드시 유효성 검증을 수행해야 합니다. - 파라미터를 OData 경로에 직접 삽입할 때 인젝션 공격에 주의하세요.
encodeURIComponent()를 활용하거나 OData V4 모델의 바인딩 문법을 사용하는 것이 안전합니다. - 민감한 데이터(토큰, 비밀번호 등)는 URL 파라미터로 전달하지 마세요. 해시 값은 브라우저 히스토리에 남습니다.
성능 최적화
- 타겟의 뷰는 기본적으로 지연 로딩(lazy loading)됩니다. 처음 해당 라우트에 접근할 때만 뷰를 생성하고, 이후에는 캐시된 인스턴스를 재사용합니다.
- 뷰가 매우 무거운 경우
async: true설정을 고려하세요. SAPUI5 1.90 이상에서는 비동기 뷰 로딩이 기본값입니다. attachPatternMatched핸들러에서 불필요한 API 호출을 방지하기 위해, 이전 파라미터와 비교하여 변경된 경우에만 데이터를 다시 로드하는 패턴을 권장합니다.
자주 발생하는 실수
- 라우트 순서 문제: routes 배열에서 더 구체적인 패턴을 먼저 배치해야 합니다. 범용적인 패턴이 앞에 있으면 뒤의 라우트가 매칭되지 않습니다.
- Component.js에서 initialize() 누락: manifest.json에 라우팅을 정의했더라도
this.getRouter().initialize()를 호출하지 않으면 라우터가 동작하지 않습니다. - attachPatternMatched vs attachRouteMatched 혼동: 단순 화면에서는
attachPatternMatched를 사용하세요.attachRouteMatched는 중첩 라우팅에서 부모 라우트 매칭까지 감지해야 할 때만 사용합니다.
참고 자료
- SAP Help — Routing and Navigation Overview
- SAPUI5 SDK — Walkthrough Tutorial (Routing)
- SAPUI5 SDK — Optional/Mandatory Route Parameters
- SAP Help — Router API and Configuration
- SAP Help — Handling Navigation and Lifecycle
- SAPUI5 API Reference — sap.ui.core.routing.Router
- SAPUI5 SDK — Routing and Navigation Concept