feat(pwa): PWA + TWA 화 (Android APK 빌드 대비)

- public/manifest.json + service worker(sw.js) 추가
- icon PNG 변환 (192/512/180)
- public/.well-known/assetlinks.json placeholder (Bubblewrap 빌드 후 APK 서명 SHA256 채울 자리)
- layout.tsx 에 manifest/theme-color/apple-touch-icon 메타데이터 + 서비스 워커 등록 스크립트 추가

Bubblewrap 으로 APK 빌드 시 https://www.momotogether.com/manifest.json 을 source 로 사용.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
chpark
2026-05-12 23:19:42 +09:00
parent 7d7b22f388
commit b343565bc1
7 changed files with 109 additions and 2 deletions
+10
View File
@@ -0,0 +1,10 @@
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.momotogether.erp",
"sha256_cert_fingerprints": ["REPLACE_WITH_APK_SHA256_AFTER_BUILD"]
}
}
]
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+38
View File
@@ -0,0 +1,38 @@
{
"name": "모모유통 ERP",
"short_name": "모모ERP",
"description": "모모유통 유통관리 ERP",
"start_url": "/",
"scope": "/",
"display": "standalone",
"orientation": "portrait",
"background_color": "#ffffff",
"theme_color": "#1f2937",
"lang": "ko",
"icons": [
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any"
},
{
"src": "/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}
+27
View File
@@ -0,0 +1,27 @@
// 모모유통 ERP — Service Worker (PWA install criteria 충족용)
const CACHE = 'momo-erp-v1';
const PRECACHE = ['/', '/manifest.json', '/icon-192.png', '/icon-512.png'];
self.addEventListener('install', (e) => {
e.waitUntil(caches.open(CACHE).then((c) => c.addAll(PRECACHE)).catch(() => {}));
self.skipWaiting();
});
self.addEventListener('activate', (e) => {
e.waitUntil(
caches.keys().then((keys) =>
Promise.all(keys.filter((k) => k !== CACHE).map((k) => caches.delete(k)))
)
);
self.clients.claim();
});
// API 요청은 항상 네트워크 (캐시 안 함). 정적 자원만 캐시.
self.addEventListener('fetch', (e) => {
const url = new URL(e.request.url);
if (url.pathname.startsWith('/api/')) return;
if (e.request.method !== 'GET') return;
e.respondWith(
fetch(e.request).catch(() => caches.match(e.request))
);
});
+34 -2
View File
@@ -1,9 +1,32 @@
import type { Metadata } from "next";
import type { Metadata, Viewport } from "next";
import Script from "next/script";
import "./globals.css";
export const metadata: Metadata = {
title: "모모유통 | 유통관리 ERP",
description: "모모유통 유통관리 ERP",
manifest: "/manifest.json",
applicationName: "모모유통 ERP",
appleWebApp: {
capable: true,
title: "모모ERP",
statusBarStyle: "default",
},
icons: {
icon: [
{ url: "/icon-192.png", sizes: "192x192", type: "image/png" },
{ url: "/icon-512.png", sizes: "512x512", type: "image/png" },
],
apple: [{ url: "/icon-180.png", sizes: "180x180", type: "image/png" }],
},
};
export const viewport: Viewport = {
themeColor: "#1f2937",
width: "device-width",
initialScale: 1,
maximumScale: 1,
userScalable: false,
};
export default function RootLayout({
@@ -19,7 +42,16 @@ export default function RootLayout({
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css"
/>
</head>
<body className="h-full">{children}</body>
<body className="h-full">
{children}
<Script id="sw-register" strategy="afterInteractive">
{`if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').catch(() => {});
});
}`}
</Script>
</body>
</html>
);
}