React 애플리케이션에서 특정 URL을 새 창으로 열고, 그 창의 변화를 추적하는 기능이 필요한 경우가 있습니다. 이 글에서는 새 창 열기, URL 가져오기, 리디렉션 감지 등 브라우저 창 제어와 관련된 다양한 방법을 알아보겠습니다.
새 창 열기 기본 방법
React에서 새 창을 여는 가장 기본적인 방법은 window.open()
메서드를 사용하는 것입니다.
function OpenWindowButton() {
const openNewWindow = () => {
window.open('https://example.com', '_blank');
};
return (
<button onClick={openNewWindow}>
새 창에서 열기
</button>
);
}
window.open()
메서드는 다음과 같은 매개변수를 받습니다:
- 첫 번째 매개변수: 열고자 하는 URL
- 두 번째 매개변수: 창 이름 또는 타겟(
_blank
,_self
등) - 세 번째 매개변수: 창 특성(크기, 위치 등)
창 참조 저장 및 제어하기
새 창을 연 후 그 창을 제어하려면 window.open()
의 반환값을 저장해야 합니다.
function ControlledWindowButton() {
const [windowRef, setWindowRef] = useState(null);
const openNewWindow = () => {
const newWindow = window.open('https://example.com', '_blank', 'width=600,height=400');
setWindowRef(newWindow);
};
const checkWindowLocation = () => {
if (windowRef && !windowRef.closed) {
try {
const currentUrl = windowRef.location.href;
console.log('현재 URL:', currentUrl);
} catch (error) {
console.error('URL에 접근할 수 없습니다:', error);
}
} else {
console.log('창이 닫혔거나 참조가 없습니다.');
}
};
return (
<div>
<button onClick={openNewWindow}>새 창 열기</button>
<button onClick={checkWindowLocation}>현재 URL 확인</button>
</div>
);
}
동일 출처 정책(Same-Origin Policy) 이해하기
브라우저의 보안 메커니즘인 동일 출처 정책으로 인해, 다른 도메인으로 열린 창의 위치나 컨텐츠에 접근하는 것은 제한됩니다. 이를 우회하는 몇 가지 방법이 있습니다.
1. 메시지 전달(postMessage) 활용
부모 창과 자식 창 사이에 메시지를 주고받는 방법입니다.
부모 창(React 앱):
function PostMessageExample() {
const [childUrl, setChildUrl] = useState('');
const [windowRef, setWindowRef] = useState(null);
// 새 창 열기
const openWindow = () => {
// 자체 도메인의 페이지를 먼저 열고, 그 페이지에서 리디렉션
const newWindow = window.open('/relay-page.html', '_blank');
setWindowRef(newWindow);
// 메시지 수신 리스너
window.addEventListener('message', (event) => {
// 출처 검증은 보안상 중요
if (event.origin === window.location.origin) {
setChildUrl(event.data.url);
}
});
};
return (
<div>
<button onClick={openWindow}>중계 페이지를 통해 새 창 열기</button>
{childUrl && <p>자식 창 URL: {childUrl}</p>}
</div>
);
}
relay-page.html (같은 도메인에 있는 중계 페이지):
<!DOCTYPE html>
<html>
<head>
<title>Relay Page</title>
</head>
<body>
<script>
// 실제 대상 페이지로 리디렉션
window.location.href = 'https://example.com';
// URL이 변경될 때마다 부모 창에 메시지 전송
setInterval(() => {
if (window.opener) {
window.opener.postMessage({ url: window.location.href }, window.opener.origin);
}
}, 500);
// 페이지 로드 완료 시 메시지 전송
window.addEventListener('load', () => {
if (window.opener) {
window.opener.postMessage({ url: window.location.href }, window.opener.origin);
}
});
</script>
</body>
</html>
2. 프록시 서버 활용
서버 측에서 대상 URL의 내용을 가져와 자신의 도메인에서 제공하는 방법입니다.
function ProxyExample() {
const [proxyData, setProxyData] = useState(null);
const fetchViaProxy = async () => {
try {
// 백엔드 프록시 API 호출
const response = await fetch('/api/proxy?url=https://example.com');
const data = await response.json();
setProxyData(data);
} catch (error) {
console.error('프록시 요청 실패:', error);
}
};
return (
<div>
<button onClick={fetchViaProxy}>프록시로 가져오기</button>
{proxyData && (
<div>
<p>타이틀: {proxyData.title}</p>
<p>URL: {proxyData.url}</p>
<p>리디렉션 여부: {proxyData.wasRedirected ? 'Yes' : 'No'}</p>
</div>
)}
</div>
);
}
서버 측 프록시 예시 (Node.js/Express):
const express = require('express');
const axios = require('axios');
const cheerio = require('cheerio');
const app = express();
app.get('/api/proxy', async (req, res) => {
try {
const targetUrl = req.query.url;
// 최초 URL
const originalUrl = targetUrl;
// 리디렉션을 따라가도록 설정
const response = await axios.get(targetUrl, {
maxRedirects: 5,
validateStatus: status => status < 400
});
// 최종 URL
const finalUrl = response.request.res.responseUrl || targetUrl;
// HTML 파싱
const $ = cheerio.load(response.data);
const title = $('title').text();
res.json({
url: finalUrl,
originalUrl,
wasRedirected: originalUrl !== finalUrl,
title,
statusCode: response.status
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => {
console.log('Proxy server running on port 3000');
});
새 창 상태 모니터링
새 창이 열려있는지, 닫혔는지 주기적으로 확인하는 방법입니다.
function WindowMonitor() {
const [windowRef, setWindowRef] = useState(null);
const [isWindowOpen, setIsWindowOpen] = useState(false);
useEffect(() => {
let interval;
if (windowRef) {
interval = setInterval(() => {
if (windowRef.closed) {
setIsWindowOpen(false);
clearInterval(interval);
} else {
setIsWindowOpen(true);
}
}, 500);
}
return () => {
if (interval) clearInterval(interval);
};
}, [windowRef]);
const openWindow = () => {
const newWindow = window.open('https://example.com', '_blank');
setWindowRef(newWindow);
};
return (
<div>
<button onClick={openWindow}>새 창 열기</button>
<p>창 상태: {isWindowOpen ? '열림' : '닫힘'}</p>
</div>
);
}
특정 도메인으로 리디렉션 감지하기
사용자가 인증 과정을 위해 외부 서비스로 리디렉션된 후 다시 돌아오는 시나리오에서 유용한 방법입니다.
function RedirectDetector() {
const [redirected, setRedirected] = useState(false);
useEffect(() => {
// URL 파라미터에서 리디렉션 여부 확인
const params = new URLSearchParams(window.location.search);
if (params.get('redirected') === 'true') {
setRedirected(true);
}
}, []);
const openAuthWindow = () => {
// 인증 서비스로 리디렉션되고, 완료 후 현재 페이지로 돌아오도록 설정
const callbackUrl = encodeURIComponent(`${window.location.origin}${window.location.pathname}?redirected=true`);
window.open(`https://auth-service.com/login?callback=${callbackUrl}`, '_blank');
};
return (
<div>
<button onClick={openAuthWindow}>인증하기</button>
{redirected && <p>인증 완료! 리디렉션되었습니다.</p>}
</div>
);
}
안전한 창 제어를 위한 실용적인 훅
위에서 설명한 기능들을 조합한 유용한 커스텀 훅입니다.
function useWindowControl() {
const [windowRef, setWindowRef] = useState(null);
const [isWindowOpen, setIsWindowOpen] = useState(false);
const [lastKnownUrl, setLastKnownUrl] = useState('');
// 창 열기
const openWindow = (url, name = '_blank', features = '') => {
const newWindow = window.open(url, name, features);
setWindowRef(newWindow);
setIsWindowOpen(true);
setLastKnownUrl(url);
return newWindow;
};
// 창 닫기
const closeWindow = () => {
if (windowRef && !windowRef.closed) {
windowRef.close();
setIsWindowOpen(false);
}
};
// URL 변경 시도
const navigateTo = (url) => {
if (windowRef && !windowRef.closed) {
try {
windowRef.location.href = url;
setLastKnownUrl(url);
return true;
} catch (error) {
console.error('URL 변경 실패:', error);
return false;
}
}
return false;
};
// 창 상태 모니터링
useEffect(() => {
let interval;
if (windowRef) {
interval = setInterval(() => {
try {
if (windowRef.closed) {
setIsWindowOpen(false);
clearInterval(interval);
} else {
// 같은 출처일 경우에만 URL 접근 시도
try {
const currentUrl = windowRef.location.href;
if (currentUrl !== lastKnownUrl) {
setLastKnownUrl(currentUrl);
}
} catch (e) {
// 다른 출처로 이동한 경우 에러가 발생하지만 무시
}
}
} catch (e) {
// 창 참조에 문제가 있는 경우
setIsWindowOpen(false);
clearInterval(interval);
}
}, 500);
}
return () => {
if (interval) clearInterval(interval);
};
}, [windowRef, lastKnownUrl]);
return {
windowRef,
isWindowOpen,
lastKnownUrl,
openWindow,
closeWindow,
navigateTo
};
}
사용 예시:
function WindowControlExample() {
const {
isWindowOpen,
lastKnownUrl,
openWindow,
closeWindow,
navigateTo
} = useWindowControl();
return (
<div>
<button onClick={() => openWindow('https://example.com')}>
새 창 열기
</button>
<button
onClick={() => navigateTo('https://example.com/page2')}
disabled={!isWindowOpen}
>
페이지2로 이동
</button>
<button onClick={closeWindow} disabled={!isWindowOpen}>
창 닫기
</button>
{isWindowOpen && (
<p>창이 열려 있습니다. 마지막 URL: {lastKnownUrl}</p>
)}
</div>
);
}
보안 및 사용자 경험 고려사항
새 창 제어 시 고려해야 할 중요한 사항들:
- 팝업 차단기: 사용자의 브라우저 팝업 차단기가 활성화되어 있으면
window.open()
호출이 차단될 수 있습니다. 사용자 상호작용(클릭 등)에 응답하여 창을 여는 것이 가장 안전합니다. - 보안 제한: 다른 출처의 창에 접근하는 것은 제한되어 있으므로, postMessage나 서버 프록시와 같은 안전한 통신 방법을 사용해야 합니다.
- 사용자 추적 고려: 새 창의 URL이나 활동을 추적하는 기능은 사용자 프라이버시와 관련이 있을 수 있으므로, 목적을 명확히 하고 필요한 경우에만 사용하세요.
- 접근성: 새 창을 열 때는 항상 사용자가 이를 인지할 수 있게 해야 합니다. 예상치 못한 창 열기는 사용자 경험을 해칠 수 있습니다.
결론
React에서 새 창을 열고 제어하는 방법에는 여러 가지가 있으며, 각각 장단점이 있습니다. 동일 출처 정책과 같은 보안 제한을 이해하고, 목적에 맞는 접근 방식을 선택하는 것이 중요합니다. 특히 인증 흐름이나 외부 서비스 통합과 같은 복잡한 시나리오에서는 이 글에서 설명한 다양한 기법을 조합하여 사용하면 효과적입니다.
보안, 성능, 사용자 경험을 균형 있게 고려하여 브라우저 창 제어 기능을 구현한다면, 더 풍부하고 유연한 웹 애플리케이션을 개발할 수 있을 것입니다.
'TypeScript' 카테고리의 다른 글
Next.js에서 cookies-next로 쿠키 관리하기 (2) | 2025.03.22 |
---|---|
tailwind CSS : 유틸리티 기반 CSS 프레임워크 (0) | 2025.03.01 |
Zod: TypeScript 스키마 검증 (0) | 2025.02.20 |
React Hook Form: 폼 상태 관리 (0) | 2025.02.19 |
Node-Cache : 서버 성능 최적화 (0) | 2025.02.17 |