From 0922c7de1764583006207cea5427713df5e2230a Mon Sep 17 00:00:00 2001
From: withleche <106927728+whoisrey@users.noreply.github.com>
Date: Sat, 21 Dec 2024 16:54:48 +0900
Subject: [PATCH] Update README.md
---
README.md | 282 ++++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 240 insertions(+), 42 deletions(-)
diff --git a/README.md b/README.md
index 9d7f906..a524907 100644
--- a/README.md
+++ b/README.md
@@ -41,13 +41,21 @@ _이러한 문제를 해결하기 위해 3D 가상 공간에서 스피커를 자
- [(3) 천장과 바닥의 소리 차이: 주파수](#3-%EC%B2%9C%EC%9E%A5%EA%B3%BC-%EB%B0%94%EB%8B%A5%EC%9D%98-%EC%86%8C%EB%A6%AC-%EC%B0%A8%EC%9D%B4-%EC%A3%BC%ED%8C%8C%EC%88%98)
- [2. 사용자 편의성을 위한 도전들](#2-%EC%82%AC%EC%9A%A9%EC%9E%90-%ED%8E%B8%EC%9D%98%EC%84%B1%EC%9D%84-%EC%9C%84%ED%95%9C-%EB%8F%84%EC%A0%84%EB%93%A4)
- [2-1. 잊어버려도 괜찮아요, 자동 저장](#2-1-%EC%9E%8A%EC%96%B4%EB%B2%84%EB%A0%A4%EB%8F%84-%EA%B4%9C%EC%B0%AE%EC%95%84%EC%9A%94-%EC%9E%90%EB%8F%99-%EC%A0%80%EC%9E%A5)
- - [(1) 저장 버튼만 존재하는 불편한 세상](#1-%EC%A0%80%EC%9E%A5-%EB%B2%84%ED%8A%BC%EB%A7%8C-%EC%A1%B4%EC%9E%AC%ED%95%98%EB%8A%94-%EB%B6%88%ED%8E%B8%ED%95%9C-%EC%84%B8%EC%83%81)
- - [(2) 자동 저장 구현하기](#2-%EC%9E%90%EB%8F%99-%EC%A0%80%EC%9E%A5-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0)
- - [(3) 자동 저장이 가져다주는 편리함](#3-%EC%9E%90%EB%8F%99-%EC%A0%80%EC%9E%A5%EC%9D%B4-%EA%B0%80%EC%A0%B8%EB%8B%A4%EC%A3%BC%EB%8A%94-%ED%8E%B8%EB%A6%AC%ED%95%A8)
+ - [(1) [고민] 저장 버튼만 존재하는 불편한 세상](#1-%EA%B3%A0%EB%AF%BC-%EC%A0%80%EC%9E%A5-%EB%B2%84%ED%8A%BC%EB%A7%8C-%EC%A1%B4%EC%9E%AC%ED%95%98%EB%8A%94-%EB%B6%88%ED%8E%B8%ED%95%9C-%EC%84%B8%EC%83%81)
+ - [(2) [구현] 자동 저장이 실행될 조건과 저장할 위치](#2-%EA%B5%AC%ED%98%84-%EC%9E%90%EB%8F%99-%EC%A0%80%EC%9E%A5%EC%9D%B4-%EC%8B%A4%ED%96%89%EB%90%A0-%EC%A1%B0%EA%B1%B4%EA%B3%BC-%EC%A0%80%EC%9E%A5%ED%95%A0-%EC%9C%84%EC%B9%98)
+ - [(3) [결과] 자동 저장이 가져다주는 편리한 세상](#3-%EA%B2%B0%EA%B3%BC-%EC%9E%90%EB%8F%99-%EC%A0%80%EC%9E%A5%EC%9D%B4-%EA%B0%80%EC%A0%B8%EB%8B%A4%EC%A3%BC%EB%8A%94-%ED%8E%B8%EB%A6%AC%ED%95%9C-%EC%84%B8%EC%83%81)
- [2-2. 빠른 속도의 비밀, 프리로드](#2-2-%EB%B9%A0%EB%A5%B8-%EC%86%8D%EB%8F%84%EC%9D%98-%EB%B9%84%EB%B0%80-%ED%94%84%EB%A6%AC%EB%A1%9C%EB%93%9C)
- - [(1) 느린 네트워크를 극복하기 위한 고민](#1-%EB%8A%90%EB%A6%B0-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC%EB%A5%BC-%EA%B7%B9%EB%B3%B5%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%EA%B3%A0%EB%AF%BC)
- - [(2) 프리로드 구현하기](#2-%ED%94%84%EB%A6%AC%EB%A1%9C%EB%93%9C-%EA%B5%AC%ED%98%84%ED%95%98%EA%B8%B0)
- - [(3) 프리로드가 가져다주는 편리함](#3-%ED%94%84%EB%A6%AC%EB%A1%9C%EB%93%9C%EA%B0%80-%EA%B0%80%EC%A0%B8%EB%8B%A4%EC%A3%BC%EB%8A%94-%ED%8E%B8%EB%A6%AC%ED%95%A8)
+ - [(1) [고민] 느린 네트워크를 극복하기 위한 고민](#1-%EA%B3%A0%EB%AF%BC-%EB%8A%90%EB%A6%B0-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC%EB%A5%BC-%EA%B7%B9%EB%B3%B5%ED%95%98%EA%B8%B0-%EC%9C%84%ED%95%9C-%EA%B3%A0%EB%AF%BC)
+ - [(2) [구현] 미리 불러오고 저장까지 해준다고?](#2-%EA%B5%AC%ED%98%84-%EB%AF%B8%EB%A6%AC-%EB%B6%88%EB%9F%AC%EC%98%A4%EA%B3%A0-%EC%A0%80%EC%9E%A5%EA%B9%8C%EC%A7%80-%ED%95%B4%EC%A4%80%EB%8B%A4%EA%B3%A0)
+ - [(3) [결과] 프리로드로 느린 네트워크 환경을 극복하기](#3-%EA%B2%B0%EA%B3%BC-%ED%94%84%EB%A6%AC%EB%A1%9C%EB%93%9C%EB%A1%9C-%EB%8A%90%EB%A6%B0-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%ED%99%98%EA%B2%BD%EC%9D%84-%EA%B7%B9%EB%B3%B5%ED%95%98%EA%B8%B0)
+ - [2-3. 잊지 않고 기억해요, 사용자 인증](#2-3-%EC%9E%8A%EC%A7%80-%EC%95%8A%EA%B3%A0-%EA%B8%B0%EC%96%B5%ED%95%B4%EC%9A%94-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%9D%B8%EC%A6%9D)
+ - [(1) [고민] 새로고침할 때마다 번거로운 인증하기](#1-%EA%B3%A0%EB%AF%BC-%EC%83%88%EB%A1%9C%EA%B3%A0%EC%B9%A8%ED%95%A0-%EB%95%8C%EB%A7%88%EB%8B%A4-%EB%B2%88%EA%B1%B0%EB%A1%9C%EC%9A%B4-%EC%9D%B8%EC%A6%9D%ED%95%98%EA%B8%B0)
+ - [(2) [구현] 사용자는 몰라도 되는 인증 기억 방법](#2-%EA%B5%AC%ED%98%84-%EC%82%AC%EC%9A%A9%EC%9E%90%EB%8A%94-%EB%AA%B0%EB%9D%BC%EB%8F%84-%EB%90%98%EB%8A%94-%EC%9D%B8%EC%A6%9D-%EA%B8%B0%EC%96%B5-%EB%B0%A9%EB%B2%95)
+ - [(3) [결과] 사용자 인증 경험을 부드럽게 이어갑니다.](#3-%EA%B2%B0%EA%B3%BC-%EC%82%AC%EC%9A%A9%EC%9E%90-%EC%9D%B8%EC%A6%9D-%EA%B2%BD%ED%97%98%EC%9D%84-%EB%B6%80%EB%93%9C%EB%9F%BD%EA%B2%8C-%EC%9D%B4%EC%96%B4%EA%B0%91%EB%8B%88%EB%8B%A4)
+ - [2-4. 잘 될거야, 낙관적 업데이트](#2-4-%EC%9E%98-%EB%90%A0%EA%B1%B0%EC%95%BC-%EB%82%99%EA%B4%80%EC%A0%81-%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8)
+ - [(1) [고민] 서버의 응답을 기다려야만 하는 상황](#1-%EA%B3%A0%EB%AF%BC-%EC%84%9C%EB%B2%84%EC%9D%98-%EC%9D%91%EB%8B%B5%EC%9D%84-%EA%B8%B0%EB%8B%A4%EB%A0%A4%EC%95%BC%EB%A7%8C-%ED%95%98%EB%8A%94-%EC%83%81%ED%99%A9)
+ - [(2) [구현] 화면은 바꿨는데 만약 서버 요청에 실패한다면?](#2-%EA%B5%AC%ED%98%84-%ED%99%94%EB%A9%B4%EC%9D%80-%EB%B0%94%EA%BF%A8%EB%8A%94%EB%8D%B0-%EB%A7%8C%EC%95%BD-%EC%84%9C%EB%B2%84-%EC%9A%94%EC%B2%AD%EC%97%90-%EC%8B%A4%ED%8C%A8%ED%95%9C%EB%8B%A4%EB%A9%B4)
+ - [(3) [결과] 서버의 응답을 기다리지 않아도 되는 합리적인 이유](#3-%EA%B2%B0%EA%B3%BC-%EC%84%9C%EB%B2%84%EC%9D%98-%EC%9D%91%EB%8B%B5%EC%9D%84-%EA%B8%B0%EB%8B%A4%EB%A6%AC%EC%A7%80-%EC%95%8A%EC%95%84%EB%8F%84-%EB%90%98%EB%8A%94-%ED%95%A9%EB%A6%AC%EC%A0%81%EC%9D%B8-%EC%9D%B4%EC%9C%A0)
- [3. 구현하며 배운 점들](#3-%EA%B5%AC%ED%98%84%ED%95%98%EB%A9%B0-%EB%B0%B0%EC%9A%B4-%EC%A0%90%EB%93%A4)
@@ -271,60 +279,101 @@ _cos_ 함수 활용 방식은 아래와 같습니다.
## 2. 사용자 편의성을 위한 도전들
-이 프로젝트에서의 사용자 경험을 증진하기 위해, 다음과 같은 기능을 추가하였습니다.
+이 프로젝트에서 사용자 편의성을 증진시키기 위해, 다음과 같은 기능을 추가하였습니다.
-- **자동 저장 기능**: 시스템 오류, 저장 버튼 누락 등 예상치 못한 상황 속에서 사용자의 스피커 배치 **데이터를 안전하게 저장**하기 위해 자동 저장 방식을 도입하였습니다. 또한, **데이터 변경 감지**와 **중복 방지 로직**을 활용하여 효율적으로 작업을 진행하도록 구현하였습니다.
+- **자동 저장**: 시스템 오류, 저장 버튼 누락 등 예상치 못한 상황 속에서 사용자의 스피커 배치 데이터를 안전하게 저장하기 위해 **자동 저장** 방식을 도입하였습니다. 자동 저장은 사용자가 스피커의 위치를 이동하고 일정 시간동안 더 이상 움직이지 않을 때, 실행됩니다. 또한 애플리케이션의 접근성을 높이기 위해 로그인을 한 사용자의 경우 스피커의 위치 정보와 관련된 데이터는 서버로 저장하고 로그인을 하지 않은 사용자의 경우 위치 정보를 로컬 스토리지에 저장하도록 구현하였습니다.
-- **프리로드 기능**: 느린 네트워크 환경에서 발생할 수 있는 **3D 애플리케이션의 불편함을 최소화**하기 위해 필요한 3D 리소스를 사전에 로드하는 **프리로딩** 기능을 도입하였습니다. 또한, 불러온 3D 모델과 텍스처를 **캐싱**하고 **재사용**하여 불필요한 리소스 요청을 줄이도록 구현하였습니다.
+- **프리로드**: 느린 네트워크 환경에서 발생할 수 있는 3D 애플리케이션의 불편함을 최소화하기 위해 필요한 3D 리소스를 사전에 로드하는 **프리로드** 기능을 도입하였습니다. 이 기능을 구현하기 위해 프리 로드로 불러온 3D 모델과 텍스처의 경로를 캐싱하는 React Three Drei의 `useGLTF` 훅을 사용하였습니다. 경로를 캐싱하는 `useGLTF`의 동작원리를 활용하여 같은 모델이 여러 개인 경우 재사용할 수 있도록 하였고 불필요한 리소스 네트워크 요청을 줄일 수 있었습니다.
+
+- **낙관적 업데이트**: 사용자가 로그인 후 서버와 데이터를 동기화하는 과정에서 데이터를 UI에 즉시 반영하여 부드러운 사용자 경험을 제공하는 **낙관적 업데이트** 방식을 도입하였습니다. 만약 동기화에 실패할 경우, 이전 데이터로 **롤백**하여 데이터 무결성을 유지하고, 사용자에게 적절한 안내를 통해 작업 진행 상황을 명확하게 알려주었습니다. 이를 통해 서버 요청 작업이 오래 걸리더라도 일관된 데이터 처리를 보장하며 화면 업데이트가 지연되지 않도록 구현하였습니다.
+
+- **사용자 인증 상태 유지**: 사용자가 새로고침하거나 인증 만료와 같은 상황이 발생더라도 끊김 없는 인증 상태를 유지하기 위해 **Axios** **인터셉터** 로직을 구현하였습니다. 인터셉터는 요청 전에 Firebase 인증 토큰을 발급받아 모든 요청에 추가하도록 하였습니다. 만약 인증이 만료가 된 경우, 응답 후에 토큰을 갱신하거나 요청을 재시도하는 로직을 구현하여 사용자 경험에 연속성을 보장하도록 구현하였습니다. 이를 통해 사용자가 로그인 절차를 반복하는 번거로움을 제거하였습니다.
### 2-1. 잊어버려도 괜찮아요, 자동 저장
-#### (1) 저장 버튼만 존재하는 불편한 세상
+#### (1) [고민] 저장 버튼만 존재하는 불편한 세상
-만약 사용자가 스피커 배치 정보를 저장하기 위해 버튼을 눌러야만 하는 경우, 발생할 수 있는 문제점들을 아래와 같이 고민하였습니다.
+만약 저장 버튼으로만 사용자가 스피커 위치 정보를 저장할 수 있다면 어떤 일들이 발생할까요?
-1. **데이터 손실 발생**:
- 사용자가 스피커를 배치하던 중 시스템 오류, 네트워크 문제 또는 의도치 않은 브라우저 종료 등으로 인해 **작업 중인 데이터를 잃어버릴 위험**이 존재합니다. 이러한 경우, 사용자는 모든 작업을 처음부터 다시 진행해야 하는 불편함을 겪게 될 수 있습니다.
+- **데이터 손실 위험**
+ 사용자가 스피커를 배치하다가 시스템 오류, 네트워크 문제, 또는 저장 버튼 클릭을 잊고 브라우저를 닫는 경우, 작업 중인 데이터를 잃을 가능성이 큽니다. 이로 인해 사용자는 작업을 **처음부터 다시 시작해야 하는 불편함**을 겪을 수 있습니다.
-2. **저장 버튼의 불편함**:
- 저장 버튼을 수동으로 클릭해야 하는 방식은 사용자 경험을 저해할 수 있습니다. 특히, 사용자가 스피커 배치 작업에 집중하다 보면 **저장 버튼 클릭을 잊거나 번거롭게 느낄 가능성**이 높습니다. 자동 저장 기능이 없는 경우 사용자는 “저장”이라는 작업을 지속적으로 의식해야 하고, 이는 프로젝트의 목표인 직관적이고 원활한 사용자 경험을 방해한다고 생각했습니다.
+
-이러한 문제들을 해결하기 위해, 사용자가 별도의 작업 없이 데이터가 주기적으로 저장되는 기능이 필요하다고 판단하였습니다. 이를 통해 사용자가 걱정없이, 저장 버튼 클릭과 같은 반복적인 작업 없이도 스피커 배치에만 집중할 수 있는 환경을 제공하고자 **자동 저장 기능**을 도입하기로 결정하였습니다.
+
+
-#### (2) 자동 저장 구현하기
+ _로그아웃 상태에서 이용할 수 없는 저장 버튼_
-**자동 저장** 기능을 단순히 주기적으로 실행할 경우, 수시로 저장이 이루어지거나 중복 데이터가 저장되어 불필요한 작업이 발생하는 문제점을 해결하기 위해 두 가지 조건을 설정하여 모두 만족할 때만 자동 저장이 실행되도록 설계하였습니다.
+
-
-
+또한, 로그인 기능과 연동되어 있는 "현재 공간 저장" 버튼의 경우, 로그인하지 않은 사용자들은 사용할 수 없기 때문에 로그인 여부에 따른 적합한 저장 방식을 별도로 고민하게 되었습니다. 이를 위해, 버튼에 의존하지 않고 데이터 손실의 위험을 줄이며 로그인하지 않은 사용자들도 저장할 수 있는 방식으로 **자동 저장** 기능을 도입하게 되었습니다.
-
+
+
+#### (2) [구현] 자동 저장이 실행될 조건과 저장할 위치
+
+자동 저장 기능을 설계하고 구현하는데 있어 가장 중요한 부분은 **저장 조건**을 명확히 정의하는 것이었습니다. 이를 통해 중복 저장을 방지하고 불필요한 작업을 줄일 수 있었습니다.
+
+- **저장 조건**
+
+ - 사용자가 스피커의 위치나 회전 값을 변경한 경우,
+ - 변경된 정보가 기존 정보와 중복되지 않는 경우,
+ - 5초 동안 사용자가 스피커의 위치나 회전 값을 변경하지 않는 경우,
+
+이러한 조건을 모두 성립한 경우, 자동 저장을 실행합니다.
-- **5초** 동안 스피커나 리스너 배치가 **바뀌었나요?**
- 자동 저장 기능이 수시로 실행되는 경우 불필요한 연산이 발생하여 **성능 저하**를 일으킬 수 있다고 판단하였습니다. 따라서 **5초**라는 대기 시간과 **변경**이 있을 때만 처리하여 성능 저하없이 정보를 효율적으로 저장하기 위해 해당 조건을 설정하였습니다.
+5초 동안 사용자가 스피커의 위치나 회전 값을 변경하지 않는 것을 감지하고 정보를 저장하기 위해 일정 시간 이후에 콜백 함수를 실행하는 비동기 함수 `setTimeout`을 활용하기로 결정하였습니다. 하지만 리액트 환경에서 `setTimeout`만 사용할 경우, 다음과 같은 문제점을 맞닥뜨릴 수 있었습니다.
-- 데이터 베이스에 저장된 배치와 **다른가요?**
- 사용자의 스피커 배치 정보는 **데이터 베이스**로 저장됩니다. 데이터 베이스에 저장하기 위해서는 **서버**에 요청을 하는 과정을 거치는데 이 요청은 **네트워크 트래픽**과 관련이 있습니다. 이 요청이 많아진다면 서버에 부하가 생길 수 있습니다. 그리하여 중복된 스피커 배치 정보는 불필요하다고 생각하여 **서버 부하를 줄이기 위해** 해당 조건을 설정하였습니다.
+- **메모리 누수**
+ 사용자가 스피커의 위치나 회전 정보를 변경할 때마다 새로운 `setTimeout` 함수가 실행될 때, 만약 컴포넌트가 언마운트되었을 때 타이머를 정리하지 않으면 기존 타이머가 계속 남아 **메모리 누수**가 발생할 수 있습니다.
+
+이러한 문제는 같은 저장 작업이 여러 번 실행되어 **중복 저장**이 발생하거나, 사용자의 의도와 다르게 데이터가 저장되어 **데이터 무결성**을 해치는 원인이 될 수 있습니다.
+
+이 문제를 해결하기 위해 `useRef`를 `setTimeout`과 함께 활용하였습니다.
+
+```jsx
+useEffect(() => {
+ if (!isDuplicate) {
+ timeoutRef.current = setTimeout(() => {
+ saveChanges();
+ }, delay);
+ }
+
+ return () => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+ };
+}, [positions, rotations]);
+```
+
+`useRef`를 사용하여 `setTimeout`의 타이머 ID를 저장하고, `useEffect`의 클린업 함수에서 타이머를 정리하는 로직을 작성하였습니다. 이렇게 새로운 타이머가 생성되기 전에 기존 타이머를 처리함으로써 **메모리 누수**를 방지하고, **데이터 무결성**을 보장할 수 있었습니다.
-#### (3) 자동 저장이 가져다주는 편리함
+또한 더 많은 사용자가 서비스를 이용할 수 있도록 로그인을 한 사용자와 로그인을 하지 않은 사용자에게 같은 경험을 제공할 필요성을 느꼈습니다. 그리하여 저장한 정보를 담는 위치를 아래와 같이 구분하였습니다.
+
+- **서버**: 로그인한 사용자
+- **로컬 스토리지**: 로그인하지 않은 사용자
-이러한 자동 저장 기능을 통해 사용자는 예상치 못한 시스템 오류나 네트워크 문제로 인한 **데이터 손실 위험**을 줄일 수 있었습니다. 또한, 조건부 자동 저장 방식을 적용하여 불필요한 **서버 요청과 연산을 최소화**하는 데 기여할 수 있었습니다. 결과적으로, *Google Docs*와 같은 현대적인 웹 애플리케이션에서 필수적으로 사용되는 자동 저장 기능을 프로젝트에 효과적으로 도입하여 **사용자 경험을 향상**시킬 수 있었습니다.
+이렇게 저장 위치를 구분지은 이유는 로그인을 한 사용자의 경우, 인증을 통해 다른 디바이스에서도 저장한 정보를 활용하도록 구현하기 위해 **서버**를 선택하였습니다. 반면, 로그인하지 않은 사용자도 같은 브라우저 환경에서 저장한 정보를 비교적 오래 유지하기 위해 **로컬 스토리지**를 선택하게 되었습니다.
+#### (3) [결과] 자동 저장이 가져다주는 편리한 세상
+
+이러한 **자동 저장** 기능을 통해 사용자는 예상치 못한 시스템 오류나 네트워크 문제로 인한 데이터 손실 위험을 크게 줄일 수 있었습니다. 특히, 사용자가 저장 버튼을 누르는 추가적인 작업 없이도 변경 사항이 자동으로 저장되기 때문에 작업에 온전히 집중할 수 있는 환경을 제공할 수 있었습니다. 이러한 접근 방식은 *Google Docs*와 같은 현대적인 웹 애플리케이션에서 필수적으로 사용되는 자동 저장 기술을 효과적으로 도입하여 결과적으로, 사용자 경험을 향상시키는데 기여할 수 있었다고 생각합니다.
+
| Google Docs | Soundrag |
| -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
-| | |
-
-
+| | |
#### 목차👆🏼
@@ -332,41 +381,190 @@ _cos_ 함수 활용 방식은 아래와 같습니다.
### 2-2. 빠른 속도의 비밀, 프리로드
-#### (1) 느린 네트워크를 극복하기 위한 고민
+#### (1) [고민] 느린 네트워크를 극복하기 위한 고민
-이 프로젝트에서는 3D 모델과 텍스처 데이터를 반드시 필요로 하며, 이로 인해 **리소스의 로딩 시간**이 사용자 경험에 직접적인 영향을 미친다고 생각합니다. 하지만 네트워크가 느린 경우, 아래와 같이 3D 모델이나 텍스처가 로드되지 않아 사용자에게 빈 화면이 노출되어 애플리케이션이 제대로 작동하지 않는 것처럼 보일 수 있습니다.
+이 프로젝트에서 3D 리소스(모델 및 텍스처)는 매우 중요한 요소입니다. 이는 사용자가 3D 모델을 조작해야 하는 핵심 기능과 직결되기 때문입니다. 하지만 느린 네트워크 환경에서는 3D 리소스가 로드되지 않아 **빈 화면**이 사용자에게 노출되는 시간이 길어질 수 있습니다. 이러한 상황이 반복되면 사용자 경험이 심각하게 저하될 가능성이 높다고 생각합니다.
-
+
+
+_느린 네트워크 환경에서 마주한 빈 화면_
+
-이와 같은 문제는 3D 환경에서 **매끄럽지 못한 사용자 경험**을 제공할 수 있는 치명적인 단점을 초래할 위험이 있다고 판단하였습니다. 이를 해결하기 위해 애플리케이션이 시작 단계에서 3D 리소스를 미리 로드하는 **프리로드** 기능을 설계하고 도입하기로 결정하였습니다.
+이러한 문제를 해결하기 위해 3D 리소스를 최대한 빠른 시점에 로드해야할 필요가 있다고 판단하였고 **프리로드** 기능을 도입하게 되었습니다.
-#### (2) 프리로드 구현하기
+#### (2) [구현] 미리 불러오고 저장까지 해준다고?
프리로드는 애플리케이션 초기 단계에서 사용자가 탐색하게 될 필요한 리소스를 미리 불러오는 과정으로, 빈 화면 노출과 미완성 렌더링과 같은 문제점을 해결하기 위해 아래와 같이 구현하였습니다.
-- **진입 페이지 구현**:
- 애플리케이션이 실행되는 즉시, **사용자가 인터페이스를 탐색하기 전**에 필요한 모든 리소스를 백그라운드에서 로드하도록 설계하였습니다. 이 때, 초기 로딩 화면으로 **진입 페이지**를 구현하여 해당 페이지에서 백그라운드로 리소스를 미리 로드하도록 구현하였습니다.
+- **진입 페이지 구현**
+ 애플리케이션이 실행되면 가장 먼저 **진입 페이지**를 렌더링하도록 설계하였습니다.이 진입 페이지에서 사용자 인터페이스를 탐색하기 전에 필요한 모든 3D 리소스를 백그라운드에서 **프리로드**하도록 구현하였습니다. 이를 통해 사용자가 진입 페이지에서 다른 페이지로 전환하더라도 필요한 3D 모델과 텍스처가 즉시 렌더링될 수 있도록 보장하였습니다.
-- **리소스 로딩 최적화**:
- 3D 모델과 텍스처 데이터를 불러올 때, 비동기 방식으로 로드하여 리소스가 효율적으로 준비되도록 최적화하였습니다. 추가로 로드된 리소스를 메모리에 **캐싱**함으로써 동일한 리소스에 대한 **중복 네트워크 요청을 방지**하였고, 이후 화면 전환 시에도 빠르고 일관된 렌더링을 제공할 수 있게 되었습니다.
+
+
-#### (3) 프리로드가 가져다주는 편리함
+ _진입 페이지에서 프리로드 진행_
+
-이러한 프리로드 기능을 도입함과 동시에 진입 페이지를 구현하면서 3D 모델과 텍스처를 불러온 상태로 다음 페이지 전환되기 때문에 사용자가 빈 화면을 경험할 일이 사라졌습니다. 결과적으로 네트워크 상태와 관계없이 안정적으로 리소스를 불러오면서 사용자가 3D 화면을 탐색하기 전에 모든 리소스가 로드된 상태를 보장하면서 3D 화면으로의 전환 과정에서 발생할 수 있는 불편함을 최소화하여 **사용자 경험을 최적화**하였습니다.
+- **useGLTF**
+ 프리로드 기능은 **React Three Drei**의 `useGLTF` 훅을 활용하여 구현하였습니다. `useGLTF`는 **React Three Fiber**의 `useLoader`를 래핑한 훅으로, `preload` 메서드를 통해 지정된 경로의 GLTF 파일을 미리 로드하고 메모리에 캐싱하는 작업을 수행합니다. 이를 통해 동일한 리소스를 재사용하며 네트워크 요청을 줄이고, 렌더링 성능을 최적화할 수 있었습니다.
+
+ ```jsx
+ useGLTF.preload("/models/speaker.gltf");
+ useGLTF.preload("/models/listener.gltf");
+ ```
+
+`useGLTF`가 데이터를 캐싱하여 재사용하는 과정은 아래와 같이 진행됩니다.
+
+1. `useGLTF`는 경로(url)를 기반으로 3D 리소스 데이터를 네트워크 요청으로 가져옵니다.
+2. 가져온 데이터는 브라우저의 메모리 Heap에 객체 형태로 저장됩니다.
+3. **React Three Fiber**의 내부 Map 객체를 사용하여 파일 경로를 키로 데이터를 저장하고 관리합니다.
+4. 동일한 경로의 파일이 요청될 경우, 네트워크 요청을 생략하고 캐싱된 데이터를 반환합니다.
+
+
+
+#### (3) [결과] 프리로드로 느린 네트워크 환경을 극복하기
+
+이러한 프리로드 기능을 도입하고 진입 페이지를 구현함으로써, 3D 모델과 텍스처를 미리 로드된 상태로 다음 페이지로 전환할 수 있어 사용자가 빈 화면을 경험하는 시간을 효과적으로 줄일 수 있었습니다. 또한, `useGLTF`의 캐싱 동작을 통해 중복된 **네트워크 요청을 방지**하고 **렌더링 성능을 최적화**하여 더욱 원활한 사용자 경험을 제공할 수 있었습니다.
+
+| Before | After |
+| -------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
+| | |
+
+#### 목차👆🏼
+### 2-3. 잊지 않고 기억해요, 사용자 인증
+
+#### (1) [고민] 새로고침할 때마다 번거로운 인증하기
+
+만약 새로고침 시 현재 인증 상태가 초기화되어 사용자가 다시 로그인을 해야 한다면, 새로고침할 때마다 사용자가 로그인을 반복해야 하는 불편함을 느낄 수 있습니다. 이러한 **반복 인증 과정**은 사용자의 피로도를 높이고 결국 서비스 이탈로 이어질 가능성이 높다고 생각하였습니다.
+
+
-
+
+
+_새로고침할 때, 로그인 상태 유지가 안되는 상황_
+
+
+
+이를 방지하기 위해, 새로고침 시에도 인증 상태를 유지하고, 사용자가 알 수 없는 인증 토큰이 만료된 경우 자동으로 갱신하는 기능 또한 필요하다고 판단하였습니다.
+
+#### (2) [구현] 사용자는 몰라도 되는 인증 기억 방법
+
+사용자의 인증을 기억하기 위해 **Axios** 인터셉터를 활용하여 인증 처리 로직을 구현하였습니다. 물론, 사용자의 인증을 검증하는 방식은 서버 미들웨어를 통해서도 처리할 수 있습니다. 하지만 네트워크 비용 절감과 응답 속도 개선을 통해 사용자 경험을 높이기 위해, 클라이언트에서 **Axios** 인터셉터를 활용한 방식을 선택하였습니다.
+
+1. **요청 인터셉터**
+ 요청 인터셉터는 API 요청이 서버로 전송되기 전에 실행되며, 현재 로그인한 사용자의 인증 토큰을 확인하고 이를 요청 헤더에 추가합니다. 이를 통해 **모든 요청이 인증된 상태**로 전송되도록 보장하였습니다.
+
+ ```jsx
+ axiosInstance.interceptors.request.use(async (config) => {
+ const user = auth.currentUser;
+
+ if (user) {
+ const idToken = await user.getIdToken();
+ config.headers.Authorization = `Bearer ${idToken}`;
+ }
+ return config;
+ });
+ ```
+
+2. **응답 인터셉터**
+ 응답 인터셉터는 서버로부터 401 (Unauthorized) 에러 응답이 반환되었을 때 실행됩니다. 이 단계에서 인증 토큰이 만료되었음을 감지하고, 새로운 토큰을 요청하여 **갱신된 토큰**으로 실패한 요청을 재실행하도록 구현하였습니다.
+
+ ```jsx
+ axiosInstance.interceptors.response.use(
+ (response) => response,
+ async (error) => {
+ const originalRequest = error.config;
+
+ if (error.response?.status === 401 && !originalRequest._retry) {
+ originalRequest._retry = true;
+
+ const user = auth.currentUser;
+
+ if (user) {
+ const newIdToken = await user.getIdToken(true);
+ originalRequest.headers.Authorization = `Bearer ${newIdToken}`;
+
+ return axiosInstance(originalRequest);
+ }
+ }
+
+ return Promise.reject(error);
+ },
+ );
+ ```
+
+#### (3) [결과] 사용자 인증 경험을 부드럽게 이어갑니다.
+
+**Axios** 인터셉터 로직을 통해 사용자 인증을 유지하면서 새로고침하거나 페이지 이동 후에도 다시 로그인하지 않고 작업을 이어갈 수 있게 되었습니다. 이는 애플리케이션 작업에 대한 **사용자의 집중도**를 높일 수 있었고 반복 작업에 대한 **사용자의 피로도** 또한 낮출 수 있었습니다.
+
+
+
+
+
+
+_새로고침을 하더라도 로그인 상태 유지가 되는 상황_
+
+
+
+#### 목차👆🏼
+
+
+
+### 2-4. 잘 될거야, 낙관적 업데이트
+
+#### (1) [고민] 서버의 응답을 기다려야만 하는 상황
+
+자동 저장 기능에서 비로그인 사용자가 로그인을 하게 되면, 로컬 스토리지에 저장된 데이터를 서버로 동기화하는 작업이 진행됩니다. 이 과정에서 네트워크 속도가 느리거나 서버 응답 시간이 길어지면, 동기화가 완료될 때까지 **UI 업데이트가 지연**되는 문제가 발생할 수 있습니다. 이러한 상황은 사용자로 하여금 화면이 멈추거나 버벅거리는 것처럼 보이게 하여, 애플리케이션의 신뢰도를 떨어뜨릴 위험이 있습니다. 이를 해결하기 위해, 느린 네트워크 환경에서도 사용자가 불편함을 느끼지 않고 매끄럽게 애플리케이션을 사용할 수 있는 방법을 고민하게 되었습니다.
+
+
+
+
+
+_느린 서버 응답때문에 발생하는 부자연스러운 화면_
+
+
+
+
+#### (2) [구현] 화면은 바꿨는데 만약 서버 요청에 실패한다면?
+
+**낙관적 업데이트**는 사용자의 요청이 성공할 것이라고 가정하고, 서버 응답을 기다리지 않고 UI를 즉시 업데이트하는 방식입니다. 이 방식을 통해 사용자는 네트워크 상태와 관계없이 자신이 조작한 대로 화면이 반응하는 것을 볼 수 있어, 더 빠르고 매끄러운 사용자 경험을 제공하도록 구현하였습니다.
+
+- **서버 요청이 실패한다면?**
+ 하지만, UI 업데이트 후 서버 요청이 실패하면 화면과 서버 데이터의 **불일치 문제**가 발생할 수 있습니다. 화면상으로는 작업이 완료된 것처럼 보이지만, 실제 서버 데이터는 업데이트되지 않아 사용자 혼란을 초래할 수 있습니다. 이러한 사용자 혼란을 덜어주기 위해 요청 실패했을 때, 아래 두 가지 방식으로 처리하였습니다.
+
+1. **상태 롤백**: 화면과 서버 데이터 간의 불일치를 방지하기 위해 요청 전에 백업해둔 데이터를 활용하여 화면을 **이전 상태로 복원**하도록 구현하였습니다.
+2. **사용자 알림**: 서버 요청 실패 시 적절한 알림 메시지를 표시하여 사용자에게 문제 상황을 명확히 전달합니다.
+
+
+
+_동기화 진행 중, 서버 요청이 실패한 상황_
+
+
+
+
+#### (3) [결과] 서버의 응답을 기다리지 않아도 되는 합리적인 이유
+
+낙관적 업데이트를 진행하면서 사용자에게 매끄러운 화면 전환을 제공할 수 있었습니다. 뿐만 아니라, 요청 실패 시 롤백 처리를 구현하면서 화면과 서버 데이터의 불일치한 상황에 대한 걱정없이 낙관적 업데이트를 진행할 수 있었습니다. 이를 통해 서버의 응답을 기다리지 않고 화면을 먼저 업데이트하면서 느린 네트워크에서도 사용자가 스피커 배치 작업을 **부드럽고 안정적**으로 진행하도록 서비스를 제공할 수 있었습니다.
+
+
+
+
+
+_낙관적 업데이트가 적용된 동기화 과정_
+
+
#### 목차👆🏼