PUBLISHED

앱 라우터를 싫어하게 된 몇 가지 이유

작성일: 2025.07.15

앱 라우터를 싫어하게 된 몇 가지 이유

지금까지 진행한 대부분의 프로젝트에서는 Next.js의 페이지 라우터를 사용해왔지만, 이번에는 처음으로 앱 라우터(App Router)를 도입해보았다. 특별한 이유가 있었던 것은 아니고, 그저 기술적인 호기심 때문이었다. 서버 수준에서 처리되는 fetch cache나 서버 컴포넌트 개념처럼, 기존의 페이지 라우터에서는 경험할 수 없던 새로운 동작 방식을 직접 체험해보고 싶었다.

기대가 없었다고 하면 거짓말이겠지만, 솔직히 말해 그 일말의 기대조차 보기 좋게 배신당했다. 새로운 구조는 분명 매력적으로 보였지만, 실제로 마주한 현실은 생각보다 복잡하고 어수선했다. 디렉토리 구조는 지나치게 제약이 많았고, 서버 컴포넌트와 클라이언트 컴포넌트의 경계를 의식하며 코드를 짜는 일은 흐름을 자주 끊어놓았다. 페이지 라우터에서 당연하게 누리던 단순함과 예측 가능성은 사라졌고, 처음엔 신기했던 기능들이 어느새 발목을 잡는 족쇄처럼 느껴졌다.

심지어 정적 데이터가 캐시되지 않거나, 서버 컴포넌트에서 사용하던 훅이 예상치 못한 시점에 오류를 일으키는 등, 재현조차 어려운 버그들이 반복되면서 개발 흐름은 끊기기 일쑤였다. 생산성을 논하기 이전에 기본적인 신뢰조차 할 수 없는 환경이라는 생각이 들었다. 무언가 잘못되어도 왜 그런지 설명하기 어려웠고, 그 불확실성은 코드에 손을 대는 것 자체를 주저하게 만들었다.

 

내게 필요한 건 새로운 개념을 실험하는 놀이터가 아니라, 예측 가능한 방식으로 안정적으로 동작하는 개발 환경이다. 따라서 나는 — 정말로 앱 라우터만이 제공할 수 있는 특정 기능이 반드시 필요한 상황이 아니라면 — 앞으로 굳이 앱 라우터 환경에서 개발을 할 것 같지 않다. 어떤 도구든 익숙해지면 그 나름의 활용법이 생기기 마련이지만, 앱 라우터는 아직 그 단계에 도달하기엔 불안 요소가 너무 많았다. 기술적 실험은 의미 있었지만, 다음 프로젝트에선 다시 페이지 라우터를 선택하게 될 가능성이 높다. 적어도 지금으로선, 그게 더 나은 선택이라고 확신한다.

이 확신에는 나름의 근거가 있다. 앱 라우터를 꺼리게 된 데에는 단순한 감정 이상의 몇 가지 분명한 이유가 존재한다. 이 포스트에는 ─ 약간의 분노를 담아 ─ 그 이유들에 대해 이야기해보고자 한다.

 

 

예측 불가능한 동작과 너무 잦은 버그

위에서도 잠깐 언급하기는 했지만, Next.js를 사용하면서 내가 가장 크게 느낀 불편함은 바로 앱 라우터의 동작이 너무 자주, 그리고 너무 쉽게 예측에서 벗어난다는 점이다. 페이지 라우터에서는 비교적 명확하게 파악되던 동작 흐름이, 앱 라우터에 오면 갑자기 ‘이게 왜 이렇게 되지?’ 싶은 순간으로 바뀌곤 한다. 심지어 공식 문서를 그대로 따라 했음에도, 환경이나 구성 방식에 따라 전혀 다른 결과가 나오기도 했다.

앱 라우터가 제공하는 킬러 기능 중 하나인 인터셉팅 라우트 등에서도 비슷한 문제가 반복됐다. 예를 들어 모달을 띄우는 패턴에서 기존 페이지의 상태를 유지한 채 새로운 경로를 열 수 있다는 점은 분명 매력적인 기능이었다. 하지만 실제로 적용해보면 동작이 일관되지 않거나, 특정 상황에서는 아예 작동하지 않는 경우도 있었다.

 

특히 기억에 남는 건, 모달이 열리고 닫힐 때 백그라운드 페이지의 스크롤 상태가 꼬이거나, 브라우저 히스토리와 모달 상태가 어긋나는 버그였다. 이런 문제가 발생하면 내가 뭔가 잘못 구성했나 싶어서 코드를 이리저리 고쳐보게 되는데, 결국 몇 시간 뒤에 찾아낸 원인은 단순히 앱 라우터의 dev 모드에서만 발생하는 버그였던 경우도 있었다. 로컬 서버를 껐다가 다시 켜니 아무 일도 없었던 것처럼 정상 동작하기 시작하는 모습을 보면, 허탈함과 함께 의심이 든다. “내가 지금 실험실 쥐처럼 테스트받고 있는 건가?” 하는 생각 말이다.

이런 경험이 반복되다 보면, 점점 내가 만든 코드나 구조보다 Next.js 자체를 신뢰하지 못하게 되는 상황에 직면하게 된다. 프레임워크는 결국 개발자가 신뢰할 수 있어야 한다. 그런데 매번 “이게 내 코드 문제일까, 아니면 프레임워크 버그일까?”를 고민하게 만든다면, 그 도구는 아직 기본값으로 채택하기에 이르다는 뜻이다.

 

 

파편화되는 라우트 경로

앱 라우터를 사용하면서 또 하나 불편하게 느낀 점은 라우트 경로가 점점 파편화되고 관리하기 어려워진다는 것이다. 페이지 라우터 시절에는 pages 폴더 아래에 파일과 폴더 구조가 명확히 대응되기 때문에, 프로젝트 규모가 커져도 전체 라우팅 구조를 한눈에 파악하기 쉬웠다. 그러나 앱 라우터에서는 폴더 안에 layout.tsx, page.tsx, loading.tsx 등 여러 종류의 파일들이 섞여 들어가면서, 경로별 역할이 분산되고 복잡도가 크게 증가한다.

특히 여러 하위 레이아웃과 중첩된 라우트가 많아질수록, 실제 URL 경로와 파일 구조 간의 대응 관계를 머릿속에서 그리기가 쉽지 않다. 예를 들어 같은 경로 내에서도 여러 개의 레이아웃 컴포넌트가 교차하며 렌더링 흐름을 제어하는 구조는, 초보자뿐 아니라 경험 많은 개발자도 빠르게 헷갈리게 만든다. 게다가 Route Group이나 slot 같은 특수 목적 라우트 기능까지 더해지다 보니, 파일이 너무 많이 흩어져서 오히려 유지보수 비용이 늘어난다는 느낌을 지울 수 없다.

 

이런 파편화는 협업 상황에서도 문제를 일으킨다. 각 개발자가 자신이 맡은 라우트의 레이아웃이나 로딩 상태를 구현하느라 분산된 여러 파일을 수정해야 하고, 결과적으로 라우팅 변경 사항에 대해 팀원 간 이해도가 떨어지며 커뮤니케이션 비용이 증가한다. 또한, 일부 특수 파일을 잘못 위치시키거나 누락하면 전체 경로가 제대로 동작하지 않는 경우도 빈번해, 신경 써야 할 부분이 많아진다.

결국, 앱 라우터는 기능적으로는 풍부해졌지만, 라우트 경로와 컴포넌트가 지나치게 분산되면서 프로젝트 구조가 직관성을 잃고 복잡해지는 부작용을 낳았다. 안정성과 예측 가능성을 중시하는 내게는, 이런 파편화된 구조가 오히려 개발 경험을 저해하는 요소로 다가왔다.

 

 

error.tsx와 not-found.tsx의 모호함

Next.js App Router는 error.tsx와 not-found.tsx라는 두 가지 특수 파일로 서로 다른 오류 상황을 처리하도록 하고있다. 하지만 실제 개발에서는 이 둘의 경계가 모호하고 예측하기 어려운 상황이 빈번하게 발생한다. 가장 대표적인 예시는 존재하지 않는 리소스에 접근하는 경우이다. `dashboard/interview/123` 경로로 접근했는데 해당 인터뷰 데이터가 존재하지 않다면, 직관적으로는 not-found.tsx가 렌더링되어야 할 것 같지만 실제로는 서버에서 데이터를 가져오는 과정에서 발생하는 오류가 error.tsx로 전달된다. 실제로 해당 라우트가 존재하지 않는 게 아닌 이상 not-found.tsx를 랜더링하기 위해서는 notFound() 함수를 명시적으로 호출해야 한다.

 

따라서 동적 경로를 사용하는 페이지에서는 notFound() 함수를 명시적으로 호출하는 로직을 반복적으로 추가해야 하는 번거로움이 발생한다. 예를 들어 인터뷰 데이터를 조회할 때마다 interview가 존재하는지에 따라 검증 로직을 작성해야 하고, 사용자 프로필 페이지에서도 동일한 검증 로직이 필요하다. 이는 단순히 코드 중복의 문제를 넘어서 개발자가 매번 "이 상황에서는 404 처리를 해야 하나, 500 에러로 처리해야 하나?"라는 판단을 내려야 한다는 인지적 부담을 가중시킨다.

더욱이 API 호출에서 반환되는 다양한 상태 코드(404, 403, 500 등)를 어떻게 분류할지, 네트워크 오류와 리소스 부재를 어떻게 구분할지에 대한 일관된 기준이 없어 팀 내에서도 개발자마다 다른 방식으로 처리하는 경우가 생긴다. 결국 "존재하지 않는 페이지"라는 단순해 보이는 개념이 실제 구현에서는 복잡한 의사결정 트리가 되어버리는 셈이다.

 

 

클라이언트 컴포넌트로 취급되는 패턴

나는 여러 패턴을 사용해 컴포넌트의 재사용성을 극적으로 끌어올리는 데 관심이 많다. props를 조합하거나, children에 함수를 넘겨서 렌더링 책임을 외부로 위임하는 방식은 그동안 내가 즐겨 사용해온 설계 전략 중 하나였다. 특히 React에서는 이러한 패턴들이 컴포넌트의 유연성을 높이고, 비즈니스 로직과 UI를 깔끔하게 분리하는 데 큰 도움이 된다.

그런데 문제는, Next.js의 앱 라우터 환경에서는 이런 패턴이 뜻밖의 제약으로 돌아온다는 점이다. 예를 들어 children에 함수를 넘겨서 렌더링을 지연하거나 조건부로 구성하려고 하면, 해당 컴포넌트는 자동으로 클라이언트 컴포넌트로 간주된다. 서버 컴포넌트 환경에서는 이처럼 "함수형 children"을 쓰는 것만으로도 구조 전체가 클라이언트 사이드로 밀려나게 된다.

 

이는 기존의 컴포넌트 재사용 전략과 서버 중심 렌더링 최적화 사이에 끼어드는 의외의 충돌이다. 결국 재사용성과 성능 사이에서 균형을 맞추기 위해, 기존에 당연하게 써왔던 패턴을 포기하거나 아예 서버/클라이언트 컴포넌트를 이원화해서 별도로 설계해야 한다. 이런 흐름은 내가 추구해온 설계 방향과 어긋나는 느낌을 주며, 때로는 Next.js에 '나답게 코딩하는 걸 방해받는다'는 생각마저 들게 만든다.

 

 

컴포넌트 격리의 번거로움

한눈에 보기에도 단순한 레이아웃이다. 인터뷰 제목과 날짜가 표시되고, 오른쪽에는 태그와 닫기 버튼이 있다. 이 정도 구성이라면 직관적으로 "서버 컴포넌트로 만들어 재사용하면 되겠다"는 판단이 든다. 실제로도, 인터뷰 정보를 서버에서 받아오고 이를 렌더링하는 컴포넌트는 서버 컴포넌트로 구성하는 게 자연스럽다. 그러나 문제는 오른쪽 위의 X 버튼에서 시작된다.

이 닫기 버튼은 상황에 따라 다른 동작을 하게 된다. 어떤 경우엔 페이지를 닫고, 어떤 경우엔 모달을 닫고, 어떤 경우엔 라우팅만 바꾼다. 결국 onClick 핸들러가 화면마다 달라진다는 뜻인데, 이는 곧 서버 컴포넌트가 아닌 클라이언트 컴포넌트로 격리해야 한다는 결론으로 이어진다. 문제는, 이 작은 상호작용 하나 때문에 컴포넌트 전체를 클라이언트로 내려야 할지, 아니면 X 버튼만 따로 컴포넌트로 빼야 할지 결정해야 한다는 점이다.

X 버튼만 클라이언트 컴포넌트로 분리하는 방식은 기술적으로는 가능하지만, 컴포넌트의 응집력을 떨어뜨린다. 레이아웃 상 위치나 스타일이 바뀌었을 때 서버/클라이언트 컴포넌트 두 곳을 동시에 수정해야 할 수도 있고, 내부에서 상태나 props를 공유하기 어렵기 때문에 재사용성도 떨어진다. 반대로 컴포넌트 전체를 클라이언트로 전환하면 서버 컴포넌트의 장점—즉, 빠른 서버 렌더링과 초기 로딩 성능—을 포기하게 된다.

이렇듯 단순한 UI 하나를 구현하는 데도, 서버와 클라이언트 컴포넌트 사이의 경계를 어디에 둘지 계속 고민하게 만드는 상황이 반복된다. 결국 "레이아웃은 서버 컴포넌트, 상호작용은 클라이언트 컴포넌트"라는 단순한 원칙이 실제 구현 단계에선 예상보다 훨씬 복잡한 분리 작업을 요구하게 된다. 그리고 이 복잡도는 코드의 양이나 파일 수, 관리 비용으로 고스란히 되돌아온다.

 

 

혼란스러운 데이터 패칭 전략

Next.js는 fetch를 중심으로 하는 서버 중심의 데이터 패칭 모델을 강하게 밀고 있다. app router와 함께 등장한 새로운 권장 방식은 fetch가 기본적으로 캐싱을 내장하고 있고, 이를 기반으로 revalidate, cache, tags 같은 메커니즘을 제공함으로써 “추가적인 라이브러리 없이도 완전한 데이터 관리가 가능하다”고 주장한다. 그러나 실제로 개발을 하다 보면, 이 접근은 특정 유형의 UX 시나리오에서는 극도로 불편하거나, 아예 적절치 않다는 사실을 깨닫게 된다.

대표적인 예가 optimistic update와 infinite scroll이다. 두 기능 모두 즉각적인 사용자 반응성과 클라이언트 상태의 세밀한 제어가 중요한데, Next.js의 내장 fetch 전략만으로는 이를 만족시키기 어렵다. optimistic update는 사용자의 입력에 대해 먼저 UI를 갱신한 뒤, 서버 응답을 대기하거나 롤백하는 패턴인데, 이 과정에서 클라이언트 상태를 직접 관리할 수 있어야 한다. 이 경우 여전히 React Query, TanStack Query 같은 클라이언트 상태 관리 라이브러리가 훨씬 적합하다.

마찬가지로 infinite scroll도 마찬가지다. Next.js의 fetch는 페이지 단위의 서버 렌더링에 특화되어 있지만, 무한 스크롤처럼 클라이언트가 점진적으로 데이터를 불러오는 상황에서는 캐시 관리와 로딩 상태, 페이지네이션 정보 관리 등 복잡한 로직을 손수 구현해야 하는 문제가 있다. 이 역시 React Query가 제공하는 useInfiniteQuery와 같은 고수준 추상화가 훨씬 생산적이다.

결국 현실적인 선택은, 서버에서 불러오고 캐싱하는 데이터는 fetch, 사용자 상호작용이 많고 상태 중심인 데이터는 React Query로 나누는 하이브리드 구조다. 하지만 이렇게 되면 또 다시 질문이 생긴다. “이 데이터는 서버에서 가져와야 할까? 클라이언트에서 관리해야 할까?” 정답이 없고, 가이드도 없고, 팀마다 다르게 결정하게 되면 코드베이스는 금세 뒤죽박죽이 된다. Next.js의 방향성은 이해하지만, 실무에서는 여전히 fetch만으로는 부족하다는 사실이 자명하다.>

 

 

서버 액션과 데이터 계층 설계의 간극

내 경우에는 기능 단위나 도메인 단위로 API 요청을 클래스로 추상화해 관리하는 방식을 오랫동안 사용해왔다. 예를 들어 InterviewQuery, UserQuery와 같이 각 도메인에 대응되는 클래스를 만들어, 해당 도메인의 데이터 요청 로직을 모두 모아두면 훨씬 구조적이고 유지보수가 편해진다. React Query의 queryFn이나 mutationFn에 이 클래스의 메서드를 그대로 넘기는 식으로도 자연스럽게 연결이 가능하다.

그런데 앱 라우터의 서버 액션과 관련된 API를 설계하다 보면, 이런 구조에 제약이 생긴다. 대표적으로 use server를 사용하는 경우, 해당 서버 액션은 반드시 직접 export 되는 함수여야 하고, 중간에 클래스나 객체 메서드로 감싸면 동작하지 않는다. 이는 서버 액션의 제약이 Node.js 런타임에서 "직접적인 호출 가능성"에 맞춰져 있기 때문이다. 결국 서버에서 실행되는 로직과 클라이언트에서 쓰이는 클래스 기반 로직을 일관되게 관리하기가 어려워진다.

이런 제약은 데이터 계층을 통합적으로 관리하려는 시도를 방해한다. 예를 들어 같은 인터뷰 데이터를 패칭하는 함수라도, 클라이언트 컴포넌트에서 사용할 때는 react-query에 맞춰 queryFn 형식으로 클래스 메서드를 넘기고, 서버 액션에서는 함수 단위로 다시 정의하거나 별도로 래핑해야 하는 상황이 생긴다. 결국 동일한 도메인 데이터를 가져오는 코드가 서로 다른 위치에, 서로 다른 규칙으로 흩어지게 된다.

이는 단지 코드 중복의 문제가 아니라, 도메인 지식이 분산되고, 데이터 흐름을 추적하기 어려워진다는 구조적 문제로 이어진다. 도메인 중심의 설계를 지향하는 입장에서는 특히 아쉬운 지점이다. 프레임워크가 특정 추상화를 강제하면서 개발자 고유의 설계 방식과 충돌할 때, 그 선택은 결국 생산성과 유지보수성 모두에 손해를 끼칠 수 있다.

 

 

객체 인스턴스의 직렬화 한계와 도메인 모델의 무력화

Next.js 앱 라우터 환경에서 가장 당황스러웠던 경험 중 하나는, 서버에서 도메인별 클래스로 데이터를 가공한 뒤 그 인스턴스를 클라이언트로 넘기려 할 때 발생하는 문제였다. 나는 오랫동안 "데이터는 도메인 모델의 인스턴스로 다룬다"는 원칙을 지켜왔다. 예를 들어, 서버에서 인터뷰 데이터를 받아오면 Interview 클래스의 인스턴스로 만들어, 그 안에 날짜 포맷팅이나 상태 판별 등 다양한 유틸리티 메서드를 담아두는 식이다. 이렇게 하면 프론트엔드 코드에서 별도의 헬퍼 함수를 만들 필요 없이, 인스턴스의 메서드를 호출하는 것만으로 대부분의 처리가 가능해진다

하지만 앱 라우터의 서버 컴포넌트 구조에서는 이런 방식이 더 이상 자연스럽게 동작하지 않는다. 서버에서 클래스로 가공한 인스턴스를 클라이언트로 넘기면, Next.js의 직렬화 과정에서 인스턴스의 메서드와 프로토타입 체인이 모두 사라진다. 결국 클라이언트에 도달한 데이터는 단순한 객체 리터럴로 변해버리고, 내가 의도했던 도메인 모델의 장점—즉, 데이터와 행위의 결합—이 완전히 무력화된다. 이를 우회하기 위해서는 몇 가지 방법을 생각해볼 수 있다. 가장 간편한 방법은 메서드 대신 순수 함수를 사용하는 것이다. 하지만 이 경우 데이터와 행위가 분리되고, 도메인 로직이 여러 계층에 흩어지면서 코드베이스의 일관성이 무너진다.

데이터를 사용하는 클라이언트 컴포넌트에서 인스턴스를 생성하는 경우에도 몇 가지 문제가 있다. 가령 서버에서 직접 가져온 데이터와 리액트 쿼리를 사용해 가져온 데이터를 인스턴스화 하는 로직이 어느정도 중복되며, 어느 계층에서 어떤 처리를 해야 하는지 명확한 기준을 세우기 어려워진다. 더 큰 문제는, 이런 구조가 팀 단위 협업에서 혼란을 가중시킨다는 점이다.

 

이처럼 앱 라우터 환경에서는 객체지향적 데이터 모델링의 이점을 온전히 살리기 어렵고, 그로 인해 코드의 일관성과 유지보수성 모두에 손실이 발생한다.

 

 

Vercel 종속성과 벤더 락인 위험

Next.js App router의 핵심 기능 대부분은 사실상 Vercel을 위해 존재한다고 해도 과언이 아니다. Edge Runtime, ISR(증분 정적 재생성), 이미지 최적화와 같은 매력적인 기능들은 Vercel 환경에서만 완벽하게, 그리고 가장 안정적으로 동작한다. 다른 클라우드 플랫폼에 배포하는 순간, 이 기능들은 성능 저하를 일으키거나 아예 사용할 수 없는 반쪽짜리가 되기 십상이다. 특히 export const runtime = 'edge' 한 줄은 Vercel의 Edge Functions에 대한 명백한 의존성을 코드에 새기는 행위이며, 이는 AWS Lambda와 같은 다른 환경으로의 이전을 사실상 포기하게 만드는 선언이다.

이러한 종속성은 프로젝트가 진행될수록 더욱 교묘하게 발목을 잡는다. vercel.json 파일에 쌓여가는 Vercel 전용 설정들, Vercel의 요금제에 맞춰 설계되는 기능들, 이 모든 것이 다른 플랫폼으로 떠나지 못하게 만드는 기술적 부채이자 족쇄이다. 결국 Vercel의 가격 정책이나 서비스 방향이 우리 프로젝트와 맞지 않게 되어도, 이미 너무 깊이 들어온 탓에 막대한 마이그레이션 비용을 감당하지 못하고 '울며 겨자 먹기'로 남아야 하는 상황에 직면할 가능성이 농후하다.

 

결론적으로 Next.js를 선택하는 것은 Vercel이라는 특정 벤더의 생태계에 우리 프로젝트의 미래를 맡기는 것과 같다. 이 편리함의 대가가 미래의 선택지를 제한하는 것이라면, 선뜻 손이 가기에는 매우 껄끄러운 선택지임이 분명하다.

 

 

결론: 이상과 현실의 거대한 괴리, 그리고 남겨진 숙제

Next.js 앱 라우터는 서버 컴포넌트라는 그럴듯한 패러다임을 내세우며, 마치 웹 개발의 미래를 선도할 것처럼 포장되었다. 그러나 현실에서 마주한 앱 라우터는 그 화려한 이상과는 정반대의 민낯을 드러낸다. 예측 불가능한 동작, 잦은 버그, 그리고 지나치게 분산된 라우트 구조는 개발자에게 혼란과 피로만을 안겨준다. 'use client' 지시어가 무분별하게 난무하는 현실은, 서버 중심이라는 명분이 얼마나 허술하게 구현되었는지를 보여주는 상징적 사례다.

문제는 단순한 구현상의 아쉬움을 넘어선다. 데이터 패칭 전략은 일관성 없이 갈팡질팡하며, 서버 액션은 명확한 경계 없이 아키텍처를 흐트러뜨린다. 직렬화의 한계는 복잡한 도메인 모델을 형해화시키고, 결국 우리는 객체가 아닌 JSON을 다루는 수준으로 퇴보하게 된다. 이 모든 와중에 개발자는 프레임워크의 구멍을 메우느라 본질적인 설계보다 트러블슈팅에 더 많은 시간을 소모한다.

게다가 이 구조는 Vercel이라는 단일 기업에 의해 설계되고 통제된다는 점에서, 기술적 종속성이라는 치명적인 위험까지 내포하고 있다. 프레임워크는 점점 특정 인프라에 최적화된 방향으로 진화하며, 그 외의 선택지를 원천적으로 차단하거나 불리하게 만든다. 이는 단순한 기술 스택의 선택이 아니라, 생태계의 다양성과 독립성을 위협하는 구조적 문제다.

 

앱 라우터는 지나치게 많은 것을, 그것도 너무 성급하게 시도했고, 그 부담을 고스란히 개발자에게 떠넘겼다. 마치 “미래는 이렇다”는 선언만 앞세운 채, 그 미래를 뒷받침할 기본기와 안정성은 뒷전으로 미룬 애플의 AI 전략을 떠올리게 하는 행보였다. 내게 있어 앱 라우터는 이제 ‘가능성’이 아니라 ‘위험성’의 상징으로 다가온다. 이러한 핵심적인 문제들이 방치된 채 개선 의지 없이 방관된다면, 앱 라우터는 미래에 대한 이상적인 청사진이 아니라 개발자 경험의 붕괴를 보여주는 교훈적 사례로 기억될 것이다.