[DEVELOP]
- [DEVELOP] DDD, TDD, BDD
- [DEVELOP] 개인정보 보호 웹사이트 구축을 위한
- [DEVELOP] 예제로 이해하는 웹 접근성 (accessibility)
- [DEVELOP] 예제로 보는 이미지 사용법 (Images)
- [DEVELOP] 예제로 보는 반응형 디자인 사용법 (Responsive Design)
- [DEVELOP] PWA 이해하기 (Progressive Web App)
- [DEVELOP] 개발 프로세스 Agile / Waterfall 이란?
- [DEVELOP] 주니어 개발자의 역습
- [DEVELOP] MCP(Model Context Protocol)
- [DEVELOP] MCP claude 적용하고 사용해보기
📱 1장: PWA(Progressive Web Apps) 시작하기
🤔 PWA가 뭔가요?
PWA는 Progressive Web Apps의 약자예요!
단어 | 의미 | 설명 |
---|---|---|
Progressive (진보적) | 점진적으로 발전해요 | 오래된 브라우저에서도 동작하고, 최신 브라우저에서는 더 많은 기능을 사용할 수 있어요 |
Web (웹) | 웹사이트예요 | 인터넷 브라우저로 접속할 수 있어요 |
Apps (앱) | 앱처럼 동작해요 | 스마트폰에 설치해서 사용할 수 있어요 |
쉽게 말하면…
“웹사이트인데 스마트폰 앱처럼 설치하고 사용할 수 있는 새로운 형태의 서비스예요!”
💡 왜 PWA가 생겼을까요?
기존 방식의 문제점 | PWA의 해결방법 |
---|---|
앱 설치가 귀찮아요 😫 | 웹사이트에서 바로 설치할 수 있어요! |
앱이 너무 커요 😱 | PWA는 매우 가벼워요 (보통 1-2MB) |
데이터를 많이 써요 📶 | 한번 받으면 오프라인에서도 사용할 수 있어요 |
업데이트가 귀찮아요 🔄 | 자동으로 최신 버전이 유지돼요 |
⭐ PWA의 특별한 점
특징 | 설명 | 예시 |
---|---|---|
📱 설치형 | 홈 화면에 설치할 수 있어요 | 크롬에서 “홈 화면에 추가” 클릭! |
🌐 오프라인 | 인터넷이 없어도 돼요 | 지하철에서도 사용 가능! |
🔔 푸시알림 | 새 소식을 알려줄 수 있어요 | “새 메시지가 도착했어요!” |
🚀 빠른 실행 | 매우 빠르게 실행돼요 | 1-2초 만에 실행! |
🌟 실제 사용 예시
- 트위터 라이트
- 일반 트위터 앱: 100MB+
- PWA 버전: 1.5MB
- 결과: 데이터 사용량 60% 감소!
- 스타벅스
- 주문 앱을 PWA로 만듦
- 오프라인에서도 메뉴 보기/주문 가능
- 결과: 데스크톱 주문 2배 증가!
📱 누구에게 좋을까요?
이런 분들께 추천해요! | 이유는… |
---|---|
📱 스마트폰 저장공간이 부족한 분 | 일반 앱의 1/10 크기예요 |
🌐 데이터가 제한적인 분 | 오프라인에서도 사용할 수 있어요 |
⚡ 빠른 실행이 필요한 분 | 설치 후 바로 실행돼요 |
🔄 자동 업데이트를 원하는 분 | 항상 최신 버전으로 유지돼요 |
💝 정리
- PWA는 웹사이트와 앱의 장점을 모두 가진 새로운 서비스예요
- 설치도 쉽고, 가볍고, 오프라인에서도 사용할 수 있어요
- 많은 기업들이 PWA를 도입하고 있어요
- 모바일 환경에서 점점 더 중요해질 거예요
📱 2장: React로 만드는 PWA
🔍 React PWA 시작하기
- CRA(Create React App)로 PWA 만들기
1
2
3
4
5
# PWA 템플릿으로 프로젝트 생성
npx create-react-app my-pwa --template cra-template-pwa
# 또는 기존 프로젝트에 PWA 추가
npm install workbox-webpack-plugin
- 기본 PWA 구조
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// App.jsx
import React, { useState, useEffect } from "react";
import { Container, Button, Alert } from "@mui/material";
function App() {
const [isInstallable, setIsInstallable] = useState(false);
const [deferredPrompt, setDeferredPrompt] = useState(null);
useEffect(() => {
// PWA 설치 가능 여부 감지
window.addEventListener("beforeinstallprompt", (e) => {
e.preventDefault();
setDeferredPrompt(e);
setIsInstallable(true);
});
}, []);
const handleInstall = async () => {
if (deferredPrompt) {
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
if (outcome === "accepted") {
console.log("PWA 설치 완료! 🎉");
}
setDeferredPrompt(null);
}
};
return (
<Container>
<h1>나의 첫 React PWA! 👋</h1>
{isInstallable && (
<Button
variant="contained"
onClick={handleInstall}
startIcon={<DownloadIcon />}
>
앱 설치하기 📱
</Button>
)}
</Container>
);
}
export default App;
- PWA 설정파일
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// public/manifest.json
{
"short_name": "React PWA",
"name": "나의 첫 React PWA",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192",
"purpose": "any maskable"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
💡 주요 기능 구현하기
- 오프라인 상태 관리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// components/OfflineDetector.jsx
import React, { useState, useEffect } from "react";
import { Snackbar } from "@mui/material";
const OfflineDetector = () => {
const [isOffline, setIsOffline] = useState(!navigator.onLine);
useEffect(() => {
const handleOnline = () => setIsOffline(false);
const handleOffline = () => setIsOffline(true);
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
return () => {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
};
}, []);
return (
<Snackbar
open={isOffline}
message="인터넷 연결이 끊겼어요! 😅"
anchorOrigin={{ vertical: "bottom", horizontal: "center" }}
/>
);
};
export default OfflineDetector;
- 푸시 알림 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// hooks/usePushNotification.js
import { useState, useEffect } from "react";
export const usePushNotification = () => {
const [permission, setPermission] = useState(Notification.permission);
const requestPermission = async () => {
try {
const result = await Notification.requestPermission();
setPermission(result);
if (result === "granted") {
// 서비스 워커 등록
const registration = await navigator.serviceWorker.ready;
// 푸시 구독
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: "YOUR_VAPID_PUBLIC_KEY",
});
console.log("푸시 알림 구독 완료! 🔔", subscription);
}
} catch (error) {
console.error("푸시 알림 에러:", error);
}
};
return { permission, requestPermission };
};
- 데이터 캐싱 with React Query
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// hooks/useOfflineData.js
import { useQuery, useQueryClient } from "react-query";
export const useOfflineData = (key, fetchFn) => {
const queryClient = useQueryClient();
return useQuery(key, fetchFn, {
// 오프라인일 때 캐시된 데이터 사용
staleTime: Infinity,
cacheTime: Infinity,
onError: (error) => {
if (!navigator.onLine) {
// 오프라인일 때 캐시된 데이터 사용
const cachedData = queryClient.getQueryData(key);
if (cachedData) {
return cachedData;
}
}
},
});
};
// 사용 예시
function PostList() {
const { data, isLoading } = useOfflineData("posts", () =>
fetch("/api/posts").then((res) => res.json())
);
if (isLoading) return <CircularProgress />;
return (
<List>
{data?.map((post) => (
<ListItem key={post.id}>{post.title}</ListItem>
))}
</List>
);
}
🎯 실제 사용 예시
완성된 PWA 컴포넌트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// components/PWAContainer.jsx
import React from "react";
import { Container, Paper } from "@mui/material";
import OfflineDetector from "./OfflineDetector";
import InstallPrompt from "./InstallPrompt";
import { usePushNotification } from "../hooks/usePushNotification";
const PWAContainer = ({ children }) => {
const { permission, requestPermission } = usePushNotification();
return (
<Container>
<Paper elevation={3} sx={{ p: 2, my: 2 }}>
<InstallPrompt />
{permission !== "granted" && (
<Button onClick={requestPermission} startIcon={<NotificationsIcon />}>
알림 허용하기 🔔
</Button>
)}
{children}
</Paper>
<OfflineDetector />
</Container>
);
};
export default PWAContainer;
✨ 정리
- React로 PWA를 만들면 컴포넌트 기반으로 깔끔하게 구현할 수 있어요
- React Query로 오프라인 데이터 관리가 쉬워져요
- Custom Hook으로 PWA 기능을 재사용할 수 있어요
- Material-UI 같은 라이브러리로 예쁜 UI를 쉽게 만들 수 있어요
📱 3장: React PWA 고급 기능 다루기
1. 🚀 성능 최적화
Code Splitting과 Lazy Loading
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// App.jsx
import React, { Suspense, lazy } from "react";
import { CircularProgress } from "@mui/material";
// 컴포넌트 지연 로딩
const HomePage = lazy(() => import("./pages/HomePage"));
const ProfilePage = lazy(() => import("./pages/ProfilePage"));
function App() {
return (
<Suspense fallback={<CircularProgress />}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/profile" element={<ProfilePage />} />
</Routes>
</Suspense>
);
}
이미지 최적화
1
2
3
4
5
6
7
8
9
10
11
// components/OptimizedImage.jsx
import React from "react";
const OptimizedImage = ({ src, alt, ...props }) => {
return (
<picture>
<source srcSet={src.replace(/\.(jpg|png)$/, ".webp")} type="image/webp" />
<img src={src} alt={alt} loading="lazy" {...props} />
</picture>
);
};
2. 🔄 백그라운드 동기화
오프라인 데이터 동기화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// hooks/useOfflineSync.js
import { useState } from "react";
import { useMutation, useQueryClient } from "react-query";
export const useOfflineSync = () => {
const [pendingActions, setPendingActions] = useState([]);
const queryClient = useQueryClient();
const syncMutation = useMutation(
async (actions) => {
for (const action of actions) {
await fetch("/api/sync", {
method: "POST",
body: JSON.stringify(action),
});
}
},
{
onSuccess: () => {
setPendingActions([]);
queryClient.invalidateQueries("userData");
},
}
);
// 오프라인 상태에서 액션 저장
const addPendingAction = (action) => {
setPendingActions((prev) => [...prev, action]);
};
// 온라인 상태가 되면 동기화
useEffect(() => {
const handleOnline = () => {
if (pendingActions.length > 0) {
syncMutation.mutate(pendingActions);
}
};
window.addEventListener("online", handleOnline);
return () => window.removeEventListener("online", handleOnline);
}, [pendingActions]);
return { addPendingAction, isPending: pendingActions.length > 0 };
};
3. 🔔 고급 푸시 알림
커스텀 푸시 알림
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// hooks/useCustomNotification.js
import { useEffect } from "react";
export const useCustomNotification = () => {
const showCustomNotification = async ({ title, body, icon, actions }) => {
const registration = await navigator.serviceWorker.ready;
await registration.showNotification(title, {
body,
icon,
badge: "/badge.png",
vibrate: [200, 100, 200],
actions: actions?.map((action) => ({
action: action.id,
title: action.title,
icon: action.icon,
})),
data: {
openUrl: window.location.origin,
},
});
};
// 알림 클릭 핸들링
useEffect(() => {
const handleNotificationClick = (event) => {
event.notification.close();
const url = event.notification.data.openUrl;
clients.openWindow(url);
};
if ("serviceWorker" in navigator) {
navigator.serviceWorker.addEventListener(
"notificationclick",
handleNotificationClick
);
}
return () => {
navigator.serviceWorker.removeEventListener(
"notificationclick",
handleNotificationClick
);
};
}, []);
return { showCustomNotification };
};
4. 📱 앱 like 경험 제공
제스처 지원
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// components/SwipeableView.jsx
import React from "react";
import { useSwipeable } from "react-swipeable";
const SwipeableView = ({ children, onSwipe }) => {
const handlers = useSwipeable({
onSwipedLeft: () => onSwipe("left"),
onSwipedRight: () => onSwipe("right"),
preventDefaultTouchmoveEvent: true,
trackMouse: true,
});
return <div {...handlers}>{children}</div>;
};
앱 상태 관리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// hooks/useAppState.js
import { useState, useEffect } from "react";
export const useAppState = () => {
const [isActive, setIsActive] = useState(true);
useEffect(() => {
const handleVisibilityChange = () => {
setIsActive(!document.hidden);
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, []);
return isActive;
};
5. 🎨 실제 사용 예시
고급 기능이 적용된 컴포넌트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// components/AdvancedPWAFeatures.jsx
import React from "react";
import { Card, Button } from "@mui/material";
import { useOfflineSync } from "../hooks/useOfflineSync";
import { useCustomNotification } from "../hooks/useCustomNotification";
import { useAppState } from "../hooks/useAppState";
const AdvancedPWAFeatures = () => {
const { addPendingAction, isPending } = useOfflineSync();
const { showCustomNotification } = useCustomNotification();
const isActive = useAppState();
const handleAction = async () => {
if (navigator.onLine) {
// 온라인 동작
await fetch("/api/action");
} else {
// 오프라인 동작
addPendingAction({
type: "ACTION",
data: { timestamp: Date.now() },
});
}
};
const sendNotification = () => {
showCustomNotification({
title: "새로운 기능!",
body: "고급 PWA 기능을 사용해보세요!",
icon: "/logo192.png",
actions: [
{ id: "explore", title: "살펴보기" },
{ id: "close", title: "닫기" },
],
});
};
return (
<Card sx={{ p: 2 }}>
<h2>고급 PWA 기능</h2>
{isPending && (
<Alert severity="info">오프라인 작업이 대기 중입니다...</Alert>
)}
<Button onClick={handleAction}>작업 실행하기</Button>
<Button onClick={sendNotification}>알림 보내기</Button>
<div>앱 상태: {isActive ? "활성" : "비활성"}</div>
</Card>
);
};
✨ 정리
- Code Splitting으로 초기 로딩 속도를 개선할 수 있어요
- 오프라인 동기화로 끊김 없는 사용자 경험을 제공해요
- 커스텀 푸시 알림으로 사용자와 더 풍부하게 소통할 수 있어요
- 앱과 같은 제스처와 상태 관리로 네이티브한 경험을 제공해요
📱 4장: React PWA 배포와 모니터링
1. 🚀 배포 준비하기
빌드 최적화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// craco.config.js
const { BundleAnalyzerPlugin } = require("webpack-bundle-analyzer");
const WorkboxWebpackPlugin = require("workbox-webpack-plugin");
module.exports = {
webpack: {
configure: (webpackConfig) => {
// 번들 크기 분석
webpackConfig.plugins.push(
new BundleAnalyzerPlugin({
analyzerMode: "static",
openAnalyzer: false,
})
);
// PWA 캐싱 전략 설정
webpackConfig.plugins.push(
new WorkboxWebpackPlugin.InjectManifest({
swSrc: "./src/service-worker.js",
maximumFileSizeToCacheInBytes: 5 * 1024 * 1024, // 5MB
})
);
return webpackConfig;
},
},
};
환경 설정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/config/pwa.config.js
export const PWA_CONFIG = {
// 개발 환경
development: {
apiUrl: "http://localhost:3000",
cacheStrategy: "NetworkFirst",
analyticsEnabled: false,
},
// 프로덕션 환경
production: {
apiUrl: "https://api.myapp.com",
cacheStrategy: "CacheFirst",
analyticsEnabled: true,
},
};
2. 📊 성능 모니터링
성능 측정 훅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// hooks/usePerformanceMetrics.js
import { useEffect, useState } from "react";
export const usePerformanceMetrics = () => {
const [metrics, setMetrics] = useState({
fcp: 0, // First Contentful Paint
lcp: 0, // Largest Contentful Paint
fid: 0, // First Input Delay
cls: 0, // Cumulative Layout Shift
});
useEffect(() => {
// Web Vitals 측정
const { getFCP, getLCP, getFID, getCLS } = require("web-vitals");
getFCP((metric) => {
setMetrics((prev) => ({ ...prev, fcp: metric.value }));
sendToAnalytics("FCP", metric);
});
getLCP((metric) => {
setMetrics((prev) => ({ ...prev, lcp: metric.value }));
sendToAnalytics("LCP", metric);
});
getFID((metric) => {
setMetrics((prev) => ({ ...prev, fid: metric.value }));
sendToAnalytics("FID", metric);
});
getCLS((metric) => {
setMetrics((prev) => ({ ...prev, cls: metric.value }));
sendToAnalytics("CLS", metric);
});
}, []);
return metrics;
};
성능 대시보드 컴포넌트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// components/PerformanceDashboard.jsx
import React from "react";
import { Card, LinearProgress, Typography } from "@mui/material";
import { usePerformanceMetrics } from "../hooks/usePerformanceMetrics";
const PerformanceDashboard = () => {
const metrics = usePerformanceMetrics();
const getScoreColor = (value, threshold) => {
return value < threshold ? "success" : "error";
};
return (
<Card sx={{ p: 3 }}>
<Typography variant="h6">성능 지표 📊</Typography>
<div>
<Typography>
First Contentful Paint (FCP)
<LinearProgress
variant="determinate"
value={metrics.fcp}
color={getScoreColor(metrics.fcp, 2000)}
/>
</Typography>
</div>
{/* 다른 메트릭스도 비슷하게 표시 */}
</Card>
);
};
3. 🔍 에러 추적과 로깅
에러 바운더리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// components/ErrorBoundary.jsx
import React from "react";
import * as Sentry from "@sentry/react";
class PWAErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Sentry로 에러 보고
Sentry.captureException(error, { extra: errorInfo });
}
render() {
if (this.state.hasError) {
return (
<div className="error-container">
<h2>앗! 뭔가 잘못됐어요 😅</h2>
<button onClick={() => window.location.reload()}>
다시 시도하기
</button>
</div>
);
}
return this.props.children;
}
}
4. 📱 버전 관리와 업데이트
업데이트 감지 및 적용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// hooks/useAppUpdate.js
import { useState, useEffect } from "react";
export const useAppUpdate = () => {
const [updateAvailable, setUpdateAvailable] = useState(false);
useEffect(() => {
// 서비스 워커 업데이트 감지
if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready.then((registration) => {
registration.addEventListener("updatefound", () => {
const newWorker = registration.installing;
newWorker.addEventListener("statechange", () => {
if (
newWorker.state === "installed" &&
navigator.serviceWorker.controller
) {
setUpdateAvailable(true);
}
});
});
});
}
}, []);
const applyUpdate = () => {
if (updateAvailable) {
window.location.reload();
}
};
return { updateAvailable, applyUpdate };
};
업데이트 알림 컴포넌트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// components/UpdateNotification.jsx
import React from "react";
import { Snackbar, Button } from "@mui/material";
import { useAppUpdate } from "../hooks/useAppUpdate";
const UpdateNotification = () => {
const { updateAvailable, applyUpdate } = useAppUpdate();
return (
<Snackbar
open={updateAvailable}
message="새로운 버전이 있어요! 🆕"
action={
<Button color="secondary" size="small" onClick={applyUpdate}>
업데이트하기
</Button>
}
/>
);
};
5. 📈 사용 통계 수집
분석 훅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// hooks/useAnalytics.js
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export const useAnalytics = () => {
const location = useLocation();
useEffect(() => {
// 페이지 뷰 추적
trackPageView(location.pathname);
// PWA 관련 이벤트 추적
trackPWAEvents();
}, [location]);
const trackPWAEvents = () => {
// 설치 추적
window.addEventListener("appinstalled", () => {
sendAnalytics("PWA", "installed");
});
// 오프라인 사용 추적
window.addEventListener("offline", () => {
sendAnalytics("PWA", "offline_usage");
});
};
};
✨ 정리
배포 체크리스트
- 빌드 최적화 확인
- 캐싱 전략 설정
- 성능 메트릭스 모니터링
- 에러 추적 시스템 구축
- 업데이트 메커니즘 구현
모니터링 포인트
- Core Web Vitals
- 오프라인 사용률
- 설치 전환율
- 에러 발생률
- 사용자 행동 패턴
📱 5장: PWA 성능 개선하기
1. 🎯 앱 성능을 높이는 방법
이미지 최적화하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// components/OptimizedImage.jsx
import React, { useState } from "react";
const OptimizedImage = ({ src, alt }) => {
const [imageLoaded, setImageLoaded] = useState(false);
// 💡 이미지가 잘 로드되었는지 확인해요
const handleImageLoad = () => {
console.log("✨ 이미지 로드 완료!");
setImageLoaded(true);
};
return (
<div style={{ position: "relative" }}>
{/* 💡 이미지가 로드되기 전에 임시 이미지를 보여줘요 */}
{!imageLoaded && (
<div className="loading-placeholder">이미지 로딩중... 🌅</div>
)}
<img
src={src}
alt={alt}
// 💡 필요할 때만 이미지를 불러와요
loading="lazy"
onLoad={handleImageLoad}
style={{
opacity: imageLoaded ? 1 : 0,
transition: "opacity 0.3s ease-in-out",
}}
/>
</div>
);
};
데이터 관리 최적화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// hooks/useDataOptimization.js
import { useState, useEffect } from "react";
export const useDataOptimization = () => {
// 💡 데이터 저장소 상태를 관리해요
const [storageInfo, setStorageInfo] = useState({
used: 0,
total: 0,
});
// 💡 저장소 정보를 가져와요
const checkStorage = async () => {
if ("storage" in navigator) {
const info = await navigator.storage.estimate();
console.log("📊 저장소 상태 체크중...");
setStorageInfo({
used: Math.round(info.usage / 1024 / 1024),
total: Math.round(info.quota / 1024 / 1024),
});
}
};
// 💡 오래된 데이터를 정리해요
const cleanOldData = async () => {
console.log("🧹 오래된 데이터 정리중...");
// 캐시 정리
const caches = await window.caches.keys();
for (let cache of caches) {
await window.caches.delete(cache);
}
// 저장소 정보 업데이트
checkStorage();
};
useEffect(() => {
checkStorage();
}, []);
return { storageInfo, cleanOldData };
};
2. 🚀 앱 실행 속도 개선하기
필요한 것만 불러오기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// App.jsx
import React, { Suspense, lazy } from "react";
// 💡 필요할 때만 페이지를 불러와요
const HomePage = lazy(() => {
console.log("🏠 홈 페이지 불러오는 중...");
return import("./pages/HomePage");
});
const ProfilePage = lazy(() => {
console.log("👤 프로필 페이지 불러오는 중...");
return import("./pages/ProfilePage");
});
function App() {
return (
// 💡 페이지가 로드되는 동안 로딩 화면을 보여줘요
<Suspense fallback={<div>로딩중... ⌛</div>}>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/profile" element={<ProfilePage />} />
</Routes>
</Suspense>
);
}
성능 모니터링
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// components/PerformanceMonitor.jsx
import React, { useEffect, useState } from "react";
const PerformanceMonitor = () => {
const [stats, setStats] = useState({
loadTime: 0,
memoryUsage: 0,
});
useEffect(() => {
// 💡 페이지 로딩 시간을 측정해요
const pageLoadTime =
window.performance.timing.loadEventEnd -
window.performance.timing.navigationStart;
// 💡 메모리 사용량을 확인해요
const memory = performance?.memory?.usedJSHeapSize || 0;
console.log("📊 성능 체크 결과:", {
로딩시간: `${pageLoadTime}ms`,
메모리: `${Math.round(memory / 1024 / 1024)}MB`,
});
setStats({
loadTime: pageLoadTime,
memoryUsage: memory,
});
}, []);
return (
<div className="performance-stats">
<h3>앱 상태 📊</h3>
<p>페이지 로딩 시간: {stats.loadTime}ms</p>
<p>메모리 사용량: {Math.round(stats.memoryUsage / 1024 / 1024)}MB</p>
</div>
);
};
3. 🔒 안전하게 데이터 저장하기
데이터 암호화
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// utils/secureStorage.js
const SecureStorage = {
// 💡 데이터를 안전하게 저장해요
saveData: (key, data) => {
try {
// 데이터를 암호화해서 저장
const encrypted = btoa(JSON.stringify(data));
localStorage.setItem(key, encrypted);
console.log("✅ 데이터 안전하게 저장 완료!");
} catch (error) {
console.error("❌ 데이터 저장 실패:", error);
}
},
// 💡 저장된 데이터를 가져와요
loadData: (key) => {
try {
const data = localStorage.getItem(key);
if (!data) return null;
// 암호화된 데이터를 해독
return JSON.parse(atob(data));
} catch (error) {
console.error("❌ 데이터 불러오기 실패:", error);
return null;
}
},
};
4. 🎨 최종 성능 개선된 앱
모든 기능을 합친 컴포넌트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// components/OptimizedApp.jsx
import React from "react";
import { useDataOptimization } from "../hooks/useDataOptimization";
import PerformanceMonitor from "./PerformanceMonitor";
const OptimizedApp = () => {
const { storageInfo, cleanOldData } = useDataOptimization();
return (
<div className="app-container">
<h1>우리 앱 📱</h1>
{/* 성능 모니터링 */}
<PerformanceMonitor />
{/* 저장소 상태 */}
<div className="storage-info">
<h3>저장소 상태 💾</h3>
<p>사용중: {storageInfo.used}MB</p>
<p>전체 공간: {storageInfo.total}MB</p>
<button onClick={cleanOldData}>오래된 데이터 정리하기 🧹</button>
</div>
{/* 이미지 최적화 예시 */}
<div className="image-container">
<h3>최적화된 이미지 🖼️</h3>
<OptimizedImage src="/example.jpg" alt="예시 이미지" />
</div>
</div>
);
};
✨ 이번 장에서 배운 내용
- 성능 개선 포인트
- 이미지는 필요할 때만 불러오기
- 데이터는 안전하게 저장하기
- 오래된 데이터는 정리하기
- 페이지는 필요할 때 불러오기
- 주요 기능
- 이미지 최적화로 빠른 로딩
- 안전한 데이터 저장
- 성능 모니터링
- 저장소 관리
- 사용자를 위한 개선점
- 빠른 페이지 로딩
- 안전한 데이터 보관
- 효율적인 저장소 사용
- 끊김 없는 사용자 경험
📱 6장: PWA 실제 서비스 적용하기
1. 🌟 실제 서비스 예시
트위터 라이트 스타일 앱 만들기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// components/TwitterLikeApp.jsx
import React, { useState } from "react";
import { List, Avatar, TextField, Button } from "@mui/material";
const TwitterLikeApp = () => {
const [posts, setPosts] = useState([]);
const [newPost, setNewPost] = useState("");
// 💡 오프라인에서도 작동하는 게시물 작성
const handlePost = () => {
const post = {
id: Date.now(),
text: newPost,
timestamp: new Date().toLocaleString(),
// 오프라인 상태 표시
isOffline: !navigator.onLine,
};
setPosts([post, ...posts]);
setNewPost("");
// 오프라인일 때 로컬에 저장
if (!navigator.onLine) {
console.log("📱 오프라인 상태: 나중에 동기화할게요!");
savePendingPost(post);
}
};
return (
<div className="twitter-like-app">
<div className="post-form">
<TextField
fullWidth
value={newPost}
onChange={(e) => setNewPost(e.target.value)}
placeholder="무슨 일이 일어나고 있나요?"
multiline
/>
<Button onClick={handlePost}>게시하기 ✨</Button>
</div>
<List>
{posts.map((post) => (
<div key={post.id} className="post">
<Avatar>👤</Avatar>
<div className="post-content">
<p>{post.text}</p>
<small>
{post.timestamp}
{post.isOffline && " (오프라인에서 작성됨)"}
</small>
</div>
</div>
))}
</List>
</div>
);
};
스타벅스 스타일 주문 앱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// components/CoffeeOrderApp.jsx
import React, { useState } from "react";
import { Card, Button } from "@mui/material";
const CoffeeOrderApp = () => {
const [cart, setCart] = useState([]);
const [orderStatus, setOrderStatus] = useState("ready");
// 💡 메뉴 데이터 (실제로는 API에서 가져올 거예요)
const menu = [
{ id: 1, name: "아메리카노", price: 4500 },
{ id: 2, name: "카페라떼", price: 5000 },
{ id: 3, name: "카푸치노", price: 5500 },
];
// 💡 오프라인에서도 주문 가능하게 만들기
const handleOrder = async () => {
setOrderStatus("processing");
try {
if (navigator.onLine) {
// 온라인: 서버에 주문 전송
await submitOrder(cart);
console.log("☕ 주문이 접수되었어요!");
} else {
// 오프라인: 로컬에 주문 저장
saveOfflineOrder(cart);
console.log("📱 오프라인 주문이 저장되었어요!");
}
setOrderStatus("completed");
setCart([]);
} catch (error) {
console.error("주문 실패:", error);
setOrderStatus("failed");
}
};
return (
<div className="coffee-order-app">
<h2>메뉴판 ☕</h2>
<div className="menu-list">
{menu.map((item) => (
<Card key={item.id} className="menu-item">
<h3>{item.name}</h3>
<p>{item.price}원</p>
<Button
onClick={() => setCart([...cart, item])}
variant="contained"
>
담기 🛒
</Button>
</Card>
))}
</div>
<div className="cart">
<h3>장바구니 🛍️</h3>
{cart.map((item) => (
<div key={item.id}>
{item.name} - {item.price}원
</div>
))}
{cart.length > 0 && (
<Button onClick={handleOrder} disabled={orderStatus === "processing"}>
{orderStatus === "processing" ? "주문중..." : "주문하기 ✨"}
</Button>
)}
</div>
</div>
);
};
2. 🔄 오프라인 동기화 구현하기
동기화 관리자
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// utils/syncManager.js
class SyncManager {
constructor() {
this.pendingActions = [];
this.setupSync();
}
// 💡 오프라인 동작 설정
setupSync() {
window.addEventListener("online", () => {
console.log("🌐 인터넷 연결됨! 동기화를 시작할게요.");
this.syncPendingActions();
});
window.addEventListener("offline", () => {
console.log("📴 오프라인 모드로 전환되었어요.");
});
}
// 💡 오프라인 작업 저장
addPendingAction(action) {
this.pendingActions.push(action);
localStorage.setItem("pendingActions", JSON.stringify(this.pendingActions));
console.log("✅ 오프라인 작업이 저장되었어요.");
}
// 💡 온라인 될 때 동기화
async syncPendingActions() {
const actions = this.pendingActions;
if (actions.length === 0) return;
console.log(`🔄 ${actions.length}개의 작업 동기화 중...`);
for (const action of actions) {
try {
await this.processAction(action);
console.log(`✅ 작업 동기화 성공: ${action.type}`);
} catch (error) {
console.error(`❌ 동기화 실패: ${action.type}`, error);
}
}
this.pendingActions = [];
localStorage.removeItem("pendingActions");
}
}
3. 📱 실제 배포 준비하기
배포 체크리스트 컴포넌트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// components/DeploymentChecker.jsx
import React, { useEffect, useState } from "react";
import { List, ListItem, Checkbox } from "@mui/material";
const DeploymentChecker = () => {
const [checks, setChecks] = useState({
manifest: false,
serviceWorker: false,
https: false,
responsive: false,
offline: false,
});
// 💡 배포 전 체크사항 확인
useEffect(() => {
const runChecks = async () => {
// manifest.json 체크
const manifestCheck =
document.querySelector('link[rel="manifest"]') !== null;
// 서비스 워커 체크
const swCheck = "serviceWorker" in navigator;
// HTTPS 체크
const httpsCheck = window.location.protocol === "https:";
// 반응형 체크
const responsiveCheck = window.matchMedia("(max-width: 768px)").matches;
// 오프라인 기능 체크
const cacheCheck = "caches" in window;
setChecks({
manifest: manifestCheck,
serviceWorker: swCheck,
https: httpsCheck,
responsive: responsiveCheck,
offline: cacheCheck,
});
};
runChecks();
}, []);
return (
<div className="deployment-checker">
<h2>배포 전 체크리스트 ✅</h2>
<List>
<ListItem>
<Checkbox checked={checks.manifest} readOnly />
manifest.json 설정
</ListItem>
<ListItem>
<Checkbox checked={checks.serviceWorker} readOnly />
서비스 워커 등록
</ListItem>
<ListItem>
<Checkbox checked={checks.https} readOnly />
HTTPS 설정
</ListItem>
<ListItem>
<Checkbox checked={checks.responsive} readOnly />
반응형 디자인
</ListItem>
<ListItem>
<Checkbox checked={checks.offline} readOnly />
오프라인 지원
</ListItem>
</List>
</div>
);
};
✨ 정리
- 실제 서비스 구현 포인트
- 오프라인 작동 지원
- 데이터 동기화
- 사용자 경험 최적화
- 안정적인 동작
- 주요 기능
- 게시물 작성/조회
- 주문 시스템
- 오프라인 동기화
- 배포 전 체크리스트
- 실제 적용시 고려사항
- 사용자 피드백 반영
- 성능 모니터링
- 오류 처리
- 지속적인 업데이트
📱 7장: PWA 유지보수와 업데이트 관리
1. 🔄 자동 업데이트 시스템
업데이트 감지 컴포넌트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// components/UpdateDetector.jsx
import React, { useEffect, useState } from "react";
import { Snackbar, Button } from "@mui/material";
const UpdateDetector = () => {
const [updateAvailable, setUpdateAvailable] = useState(false);
useEffect(() => {
// 💡 서비스 워커의 업데이트를 감지해요
if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready.then((registration) => {
registration.addEventListener("updatefound", () => {
console.log("🔄 새로운 버전이 있어요!");
setUpdateAvailable(true);
});
});
}
}, []);
// 💡 새로운 버전으로 업데이트하기
const handleUpdate = () => {
console.log("✨ 새로운 버전으로 업데이트합니다!");
window.location.reload();
};
return (
<Snackbar
open={updateAvailable}
message="새로운 버전이 있어요! 🆕"
action={
<Button color="secondary" onClick={handleUpdate}>
업데이트하기
</Button>
}
/>
);
};
2. 📊 버전 관리 시스템
버전 관리 유틸리티
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// utils/versionManager.js
class VersionManager {
constructor() {
this.currentVersion = process.env.REACT_APP_VERSION || "1.0.0";
this.setupVersionCheck();
}
// 💡 버전 정보를 확인해요
setupVersionCheck() {
console.log(`📱 현재 버전: ${this.currentVersion}`);
// 로컬 저장소에서 이전 버전 확인
const lastVersion = localStorage.getItem("appVersion");
if (lastVersion !== this.currentVersion) {
console.log("🆕 새로운 버전이 설치되었어요!");
this.handleNewVersion();
}
}
// 💡 새 버전 설치 후 필요한 작업을 처리해요
handleNewVersion() {
// 캐시 초기화
if ("caches" in window) {
caches.keys().then((names) => {
names.forEach((name) => {
caches.delete(name);
});
});
}
// 버전 정보 저장
localStorage.setItem("appVersion", this.currentVersion);
// 변경사항 안내
this.showChangelog();
}
// 💡 변경사항을 보여줘요
showChangelog() {
console.log("✨ 이번 버전의 새로운 기능들이에요!");
// 여기에 각 버전별 변경사항을 추가하세요
}
}
3. 🛠️ 유지보수 도구
상태 모니터링 컴포넌트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// components/MaintenanceTools.jsx
import React, { useState, useEffect } from "react";
import { Card, Button, LinearProgress } from "@mui/material";
const MaintenanceTools = () => {
const [stats, setStats] = useState({
cacheSize: 0,
lastUpdate: null,
errors: [],
});
// 💡 앱 상태를 주기적으로 체크해요
useEffect(() => {
const checkAppHealth = async () => {
// 캐시 크기 확인
if ("storage" in navigator) {
const estimate = await navigator.storage.estimate();
const cacheSize = Math.round(estimate.usage / 1024 / 1024);
setStats((prev) => ({
...prev,
cacheSize,
lastUpdate: new Date().toLocaleString(),
}));
}
};
// 1분마다 상태 체크
const interval = setInterval(checkAppHealth, 60000);
checkAppHealth(); // 초기 체크
return () => clearInterval(interval);
}, []);
// 💡 문제가 생긴 부분을 고쳐요
const handleMaintenance = async () => {
console.log("🛠️ 유지보수를 시작합니다...");
try {
// 캐시 정리
await caches
.keys()
.then((keys) => Promise.all(keys.map((key) => caches.delete(key))));
// 로컬 데이터 정리
const cleanupTasks = ["outdatedData", "tempFiles", "errorLogs"];
cleanupTasks.forEach((task) => {
localStorage.removeItem(task);
});
console.log("✨ 유지보수가 완료되었어요!");
} catch (error) {
console.error("유지보수 중 오류 발생:", error);
}
};
return (
<Card className="maintenance-tools">
<h2>앱 관리 도구 🛠️</h2>
<div className="stats">
<p>캐시 크기: {stats.cacheSize}MB</p>
<p>마지막 점검: {stats.lastUpdate}</p>
<LinearProgress variant="determinate" value={stats.cacheSize / 100} />
</div>
<div className="actions">
<Button variant="contained" onClick={handleMaintenance}>
유지보수 실행하기 🧹
</Button>
</div>
</Card>
);
};
4. 📝 사용자 피드백 시스템
피드백 수집 컴포넌트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// components/FeedbackSystem.jsx
import React, { useState } from "react";
import { TextField, Button, Rating, Snackbar } from "@mui/material";
const FeedbackSystem = () => {
const [feedback, setFeedback] = useState({
rating: 0,
comment: "",
category: "general",
});
// 💡 사용자의 소중한 의견을 저장해요
const handleSubmit = async () => {
console.log("📝 피드백을 저장합니다...");
try {
// 오프라인일 때는 나중에 전송하도록 저장
if (!navigator.onLine) {
const pendingFeedbacks = JSON.parse(
localStorage.getItem("pendingFeedbacks") || "[]"
);
pendingFeedbacks.push({
...feedback,
timestamp: Date.now(),
});
localStorage.setItem(
"pendingFeedbacks",
JSON.stringify(pendingFeedbacks)
);
console.log("📱 오프라인 상태: 피드백을 임시 저장했어요!");
} else {
// 온라인일 때는 바로 전송
await submitFeedback(feedback);
console.log("✅ 피드백이 전송되었어요!");
}
// 입력 폼 초기화
setFeedback({
rating: 0,
comment: "",
category: "general",
});
} catch (error) {
console.error("피드백 전송 실패:", error);
}
};
return (
<div className="feedback-system">
<h2>여러분의 의견을 들려주세요! 💌</h2>
<div className="rating-section">
<p>앱이 마음에 드시나요?</p>
<Rating
value={feedback.rating}
onChange={(_, value) =>
setFeedback((prev) => ({ ...prev, rating: value }))
}
/>
</div>
<TextField
multiline
rows={4}
value={feedback.comment}
onChange={(e) =>
setFeedback((prev) => ({ ...prev, comment: e.target.value }))
}
placeholder="자유롭게 의견을 적어주세요!"
/>
<Button
variant="contained"
onClick={handleSubmit}
disabled={!feedback.comment || !feedback.rating}
>
의견 보내기 ✉️
</Button>
</div>
);
};
✨ 정리
- 업데이트 관리
- 자동 업데이트 감지
- 버전 관리
- 변경사항 안내
- 원활한 업데이트 진행
- 유지보수 포인트
- 정기적인 상태 체크
- 캐시 관리
- 오류 모니터링
- 성능 최적화
- 사용자 소통
- 피드백 수집
- 오프라인 지원
- 변경사항 안내
- 사용자 경험 개선
📱 8장: PWA의 미래와 발전 방향
1. 🚀 최신 PWA 기술 트렌드
Project Fugu API 활용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// components/ModernPWAFeatures.jsx
import React, { useEffect, useState } from "react";
import { Card, Button } from "@mui/material";
const ModernPWAFeatures = () => {
const [features, setFeatures] = useState({
bluetooth: false,
nfc: false,
fileSystem: false,
clipboard: false,
});
// 💡 최신 기능들을 확인해요
useEffect(() => {
const checkModernFeatures = async () => {
console.log("🔍 최신 기능 지원 여부를 확인합니다...");
// Bluetooth 지원 확인
const bluetooth = "bluetooth" in navigator;
// NFC 지원 확인
const nfc = "nfc" in navigator;
// 파일 시스템 접근 지원 확인
const fileSystem = "showOpenFilePicker" in window;
// 클립보드 API 지원 확인
const clipboard = "clipboard" in navigator;
setFeatures({
bluetooth,
nfc,
fileSystem,
clipboard,
});
};
checkModernFeatures();
}, []);
return (
<Card className="modern-features">
<h2>최신 PWA 기능 🎉</h2>
<div className="feature-list">
<div className="feature-item">
<h3>블루투스 연결 {features.bluetooth ? "✅" : "❌"}</h3>
<p>주변 기기와 연결해보세요!</p>
</div>
<div className="feature-item">
<h3>NFC 태그 {features.nfc ? "✅" : "❌"}</h3>
<p>스마트 태그를 읽어보세요!</p>
</div>
<div className="feature-item">
<h3>파일 시스템 {features.fileSystem ? "✅" : "❌"}</h3>
<p>로컬 파일을 관리해보세요!</p>
</div>
<div className="feature-item">
<h3>클립보드 접근 {features.clipboard ? "✅" : "❌"}</h3>
<p>복사/붙여넣기를 활용해보세요!</p>
</div>
</div>
</Card>
);
};
2. 🎯 미래 대비 코드 작성
적응형 기능 구현
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// components/FutureProofFeatures.jsx
import React from "react";
import { Card, Switch } from "@mui/material";
const FutureProofFeatures = () => {
// 💡 브라우저 지원 여부를 확인해요
const checkFeatureSupport = (featureName) => {
switch (featureName) {
case "share":
return "share" in navigator;
case "wakeLock":
return "wakeLock" in navigator;
case "badging":
return "setAppBadge" in navigator;
case "periodicSync":
return "periodicSync" in navigator.serviceWorker;
default:
return false;
}
};
// 💡 새로운 기능을 안전하게 사용해요
const useFeature = async (featureName) => {
console.log(`🚀 ${featureName} 기능을 사용해볼게요!`);
try {
switch (featureName) {
case "share":
if (checkFeatureSupport("share")) {
await navigator.share({
title: "멋진 PWA 앱",
text: "새로운 기능을 체험해보세요!",
url: window.location.href,
});
}
break;
case "wakeLock":
if (checkFeatureSupport("wakeLock")) {
await navigator.wakeLock.request("screen");
}
break;
// 다른 기능들도 비슷하게 처리
}
} catch (error) {
console.error(`${featureName} 기능 사용 중 오류:`, error);
}
};
return (
<Card className="future-proof">
<h2>미래 대비 기능 🔮</h2>
<div className="feature-toggles">
<div className="feature-toggle">
<span>공유하기 기능</span>
<Switch
checked={checkFeatureSupport("share")}
onChange={() => useFeature("share")}
/>
</div>
<div className="feature-toggle">
<span>화면 켜짐 유지</span>
<Switch
checked={checkFeatureSupport("wakeLock")}
onChange={() => useFeature("wakeLock")}
/>
</div>
<div className="feature-toggle">
<span>앱 배지</span>
<Switch
checked={checkFeatureSupport("badging")}
onChange={() => useFeature("badging")}
/>
</div>
</div>
</Card>
);
};
3. 🌈 새로운 사용자 경험
AI 기능 통합
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// components/AIFeatures.jsx
import React, { useState } from "react";
import { Card, TextField, Button } from "@mui/material";
const AIFeatures = () => {
const [userInput, setUserInput] = useState("");
const [aiResponse, setAiResponse] = useState("");
// 💡 AI 기능을 PWA에 통합해요
const handleAIInteraction = async () => {
console.log("🤖 AI 기능을 실행합니다...");
try {
// 오프라인에서도 동작하는 기본 AI 기능
if (!navigator.onLine) {
const basicResponse = await offlineAIProcess(userInput);
setAiResponse(basicResponse);
return;
}
// 온라인에서는 고급 AI 기능 사용
const response = await fetch("/api/ai", {
method: "POST",
body: JSON.stringify({ input: userInput }),
});
const data = await response.json();
setAiResponse(data.response);
} catch (error) {
console.error("AI 처리 중 오류:", error);
setAiResponse("죄송해요, 나중에 다시 시도해주세요 😅");
}
};
return (
<Card className="ai-features">
<h2>AI 도우미 🤖</h2>
<TextField
fullWidth
value={userInput}
onChange={(e) => setUserInput(e.target.value)}
placeholder="무엇을 도와드릴까요?"
/>
<Button onClick={handleAIInteraction} variant="contained">
AI에게 물어보기 ✨
</Button>
{aiResponse && (
<div className="ai-response">
<p>{aiResponse}</p>
</div>
)}
</Card>
);
};
4. 🎨 미래형 UI/UX
제스처 기반 인터페이스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// components/FutureUI.jsx
import React, { useEffect, useState } from "react";
import { motion } from "framer-motion";
const FutureUI = () => {
const [gestureSupport, setGestureSupport] = useState(false);
const [currentTheme, setCurrentTheme] = useState("light");
// 💡 제스처 지원 여부를 확인해요
useEffect(() => {
const checkGestureSupport = () => {
const support = "ongestureend" in window;
setGestureSupport(support);
console.log(`제스처 지원 ${support ? "가능" : "불가능"} 👋`);
};
checkGestureSupport();
}, []);
// 💡 제스처로 테마를 변경해요
const handleGesture = (direction) => {
console.log(`🎨 ${direction} 제스처 감지!`);
switch (direction) {
case "up":
setCurrentTheme("light");
break;
case "down":
setCurrentTheme("dark");
break;
// 다른 제스처들도 처리
}
};
return (
<motion.div
className={`future-ui ${currentTheme}`}
onPanEnd={(e, info) => {
const direction = info.offset.y > 0 ? "down" : "up";
handleGesture(direction);
}}
>
<h2>미래형 인터페이스 ✨</h2>
<div className="gesture-area">
<p>
{gestureSupport
? "여기에서 제스처를 사용해보세요!"
: "제스처가 지원되지 않아요 😅"}
</p>
</div>
<div className="theme-preview">
<p>현재 테마: {currentTheme}</p>
<small>위/아래로 스와이프해서 테마를 바꿔보세요!</small>
</div>
</motion.div>
);
};
✨ 정리
- 새로운 기술 트렌드
- Project Fugu API
- AI 통합
- 제스처 인터페이스
- 적응형 기능
- 미래 대비 포인트
- 브라우저 지원 확인
- 대체 기능 준비
- 점진적 기능 향상
- 사용자 경험 개선
- PWA의 발전 방향
- 네이티브 앱 수준의 기능
- AI 기반 개인화
- 직관적인 인터페이스
- 더 나은 오프라인 경험
이렇게 해서 PWA의 현재와 미래에 대해 알아보았어요! PWA는 계속해서 발전하고 있으며, 웹의 미래를 이끌어갈 중요한 기술이 될 거예요. 😊