Electron 프레임워크 Nextron으로 데스크탑앱 만들어보기 chapter.2 render 프로세스의 Next.js가 동작하는 방식과 세팅시 나타날수 있는 현상들

이수연·2025년 2월 8일
1

챕터1에서는 nextron의 초기세팅, 구조, 세팅시 발생할수있는 문제에 대하여 설명하였다. 챕터2에서는 nextron에서 next js가 어떻게 동작하는지, 어떻게 세팅해야되는지 알아보자.

챕터1에서 설명한대로 nextron의 renderer프로세스의 next js는 pages/ 폴더 기준으로 작동한다. pages폴더기준으로 한다면 next js의 버전은 13버전이상이어도 문제가 없다.

nextron에서 nextjs가 동작하는방식

Nextron은 Next.js와 Electron을 결합한 프레임워크로, Electron의 BrowserWindow 내에서 Next.js 애플리케이션을 실행할 수 있도록 한다.

Electron의 구조상 브라우저 창(BrowserWindow)은 단일 URL에서 실행되며, HTML 파일이 동적으로 변경되는 방식으로 동작한다.
이로 인해 서버 측 렌더링(SSR) 방식은 Electron 앱과 맞지 않으며, Nextron에서는 정적 페이지 생성(SSG) 방식이 기본적으로 활용된다.

Nextron에서 SSR이 적합하지 않은 이유

Electron은 단일 URL 내부에서 HTML 파일이 변경되는 방식으로 동작
Electron의 BrowserWindow는 단일 HTML 파일을 로드하며, 브라우저처럼 URL을 변경하면서 페이지를 새로고침하는 방식이 아니다.
따라서 Next.js의 SSR처럼 페이지 요청마다 새로운 HTML을 생성하는 방식은 Electron의 구조와 맞지 않는다.

SSR은 서버가 필요하지만, Electron은 기본적으로 서버리스 환경
Next.js의 SSR은 클라이언트 요청 시마다 서버에서 HTML을 동적으로 생성하여 반환하는 방식
그러나 Electron은 로컬에서 실행되며 서버를 필요로 하지 않는 환경이므로 SSR이 적합하지 않음

그럼 nextron에서 csr이나 ssr은 사용할수 없을까?

next.js는 하이브리드 렌더링 방식을 이용한다. 따라서 페이지가 ssg로 구성되어있다고 해도, 내부 컴포넌트에서 csr이나 ssr의 사용은 가능하다.

아래는 실제로 페이지를 ssg로 설정하지 않았을때 나타나는 오류이다. 페이지를 찾지 못하고 있다.

나는 아래와같이 pages폴더에 web/[pagename].tsx를 만들고 이파일에서 getStaticPaths함수를 이용해서 페이지 구조를 ssg로 만들었다.


type PageParams = {
  pagename: string;
};

const availablePages = [
  "main",
  "findinfo",
  "login",
  "signup",
  "complete",
  "mypage",
  "register",
  "write_essay",
  "myessay",
  "community",
  "termsofuse",
  "essay_details",
  "unfinished_writing",
  "user_profile",
];

export const getStaticPaths = async () => {
  const paths = availablePages.map((page) => ({
    params: { pagename: page },
  }));

  return { paths, fallback: false };
};

interface RenderViewProps {
  pageName: string;
}

const RenderView: React.FC<RenderViewProps> = ({ pageName }) => {
  if (!pageName) {
    return <div>Loading...</div>;
  }
  switch (pageName) {
    case "main":
      return <ProtectedMain />;
    case "findinfo":
      return <ProtectedFindInfo />;
    case "login":
      return <Login />;
    case "termsofuse":
      return <TermsofUse />;
    case "signup":
      return <SignUp />;
    case "myessay":
      return <ProtectedMyEssay />;
    case "community":
      return <ProtectedCommunity />;
    case "complete":
      return <ProtectedComplete />;
    case "mypage":
      return <ProtectedMypage />;
    case "write_essay":
      return <ProtectedWriteEssay />;
    case "essay_details":
      return <EssayDetail />;
    case "unfinished_writing":
      return <ProtectedUnfinishedWriting />;
    case "user_profile":
      return <ProtectedUserProfile />;
    default:
      return <ProtectedMain />;
  }
};

export default RenderView;

getStaticPaths는 성능에 안좋다고 들었는데??

일반 웹사이트에서 SSG(Static Site Generation) 를 사용할 경우, 모든 페이지를 빌드 시점에 생성해야 하므로 빌드 시간이 길어지는 문제가 발생할 수 있다.
특히, getStaticPaths()를 사용하여 많은 정적 페이지를 생성할 경우, 빌드 타임이 오래 걸려 성능에 영향을 미치는 것이 사실이다.

하지만 Electron은 일반 웹사이트가 아니라 데스크톱 애플리케이션이기 때문에, 이러한 성능 문제의 영향이 상대적으로 적다.
Electron 앱은 빌드 후 실행 파일(.exe, .dmg 등)로 패키징되므로, 한 번 빌드된 HTML을 빠르게 로드할 수 있어 성능 저하가 크지 않다.

즉, 웹 애플리케이션에서 getStaticPaths()가 성능 문제를 유발할 수 있지만, Electron 환경에서는 상대적으로 부담이 적어 충분히 활용할 수 있다.

번외) electron내부의 브라우저에서 위치서비스를 가져오려 할때 제공불가 에러가 난다??

보통의 웹사이트에서는 기본적으로 제공하는 geolocation api를 이용하여 위치정보를 가져올수 있다. 그러나 Electron에서는 보안 및 개인정보 보호 문제로 인해 브라우저의 위치 서비스(navigator.geolocation)가 기본적으로 차단된다.

Electron에서 Geolocation API가 제한되는 이유는 일반적인 브라우저가 아닌 데스크톱 애플리케이션 Electron은 Chromium 기반이지만, 웹 브라우저와는 다르게 실행되는 독립적인 데스크톱 애플리케이션이다.
브라우저 환경에서는 사용자의 명시적인 동의(Permission API) 를 받아 위치 데이터를 제공하지만, Electron에서는 브라우저의 위치 서비스가 기본적으로 비활성화되어 있다. 따라서 electron에서 위치정보를 받아오려면, 메인프로세서에서 위치정보를 받아서 renderer프로세서에 전달해줘야 된다. 필자는 npm node-geocoder를 이용하여 구현 하였다.

main/helpers/create-window.ts

ipcMain.handle("get-location", async () => {
  try {
    const ipResponse = await fetch("https://api.ipify.org?format=json");
    const ipData = await ipResponse.json();
    const userIp = ipData.ip;

    const locationResponse = await fetch(
		각자의 url
    );
    const locationData = await locationResponse.json();

    return {
      city: locationData.city,
      country: locationData.country_name,
      latitude: locationData.latitude,
      longitude: locationData.longitude,
    };
  } catch (error) {
    console.error("Error fetching location:", error);
    return null;
  }
});

위와 같이 위치정보를 받아오고, main/preload파일에서 아래와 같이 정보를 받아온후
next js에서 해당하는 함수를 호출하면된다.

main/preload.ts

contextBridge.exposeInMainWorld("Electron", {
  getLocation: () => ipcRenderer.invoke("get-location"),

});
next.js 파일
  useEffect(() => {
    const fetchLocation = async () => {
      try {
        const locationData = await window.Electron.getLocation();
        if (locationData && user?.locationConsent) {
			  setBottomValue((prev) => ({
            ...prev,
            location: {
              ...prev.location,
              values: [parseLocation, ...prev.location.values.slice(1)],
            },
          }));
        }
      } catch (err) {
        console.error("Error fetching location:", err);
      }
    };

    fetchLocation();
  }, [user]);

여기서 잠깐!

next.js에서 electron의 함수를 불러올때, 타입스크립트를 쓴다면 타입 에러가 날수 있다. 아래와 같이 next.js 루트경로에 global.d.ts로 전역으로 window.Electron의 타입을 지정해야 타입에러 없이 사용이 가능하다.

declare global {
  interface Window {
    Electron: {
      showNotification: (title: string, body: any) => void; 
      getLocation: () => Promise<{ latitude: string; longitude: string } | null>;
    };
  }
}

export {};

다음 챕터에서는...

이번 챕터에서는 nextron에서 nextjs의 동작방식과 세팅시 나타날수 있는 현상들을 살펴 보았다. 다음장은 main프로세서에서의 ipcMain에 대하여 알아보도록 하겠다.

0개의 댓글