오늘은 Electron 프레임워크와 SvleteKit이라는 풀 스택 웹 프레임워크를 결합하여 데스크톱 앱을 만들어 보겠습니다. Electron과 SvelteKit을 함께 사용하는 사례는 별로 없더라고요. 그래서 SvelteKit에서 static으로 빌드하고, 그것을 다시 Electron으로 빌드하는 방식으로 구현했습니다.
목차
1. Electron+SvleteKit+TypeScript 개발 환경 구축
2. 프로젝트 폴더 구조
3. svelte.config.js
4. package.json
5. src/app.html
6. static/shared.css 예제
7. electron.cts
8. src/routes/+layout.ts
9. src/lib/href.ts
10. src/routes/+layout.svelte
11. src/routes/+page.svelte 예제
12. src/routes/about/+page.svelte 예제
13. 디버깅(npm run dev)
14. 빌드(npm run build)
15. 글 마무리
16. 참고 자료
1. Electron+SvleteKit+TypeScript 개발 환경 구축
1.1.1. Node.js가 필요합니다. Node.js가 설치되어 있지 않다면 먼저 설치합니다.
Windows PC에 Node.js 설치, npm 명령어
1.1.2. SvelteKit 프로젝트를 만들 폴더에서 터미널을 엽니다.
1.1.3. npx sv create 프로젝트명 명령을 실행합니다.
1.1.4. ‘Ok to proceed? (y)’라는 질문에 엔터를 칩니다.
1.1.5. ‘Which template would you like?’라는 질문에 SvelteKit minimal을 선택합니다.
1.1.6. ‘Add type checking with TypeScript?’라는 질문에 Yes, using TypeScript syntax를 선택합니다.
1.1.7. ‘What would you like to add to your project?’라는 질문이 나오면 방향 키를 눌러 sveltekit-adapter로 이동한 뒤 스페이스 바를 누르고, 엔터를 눌러 다음 단계로 넘어갑니다.
1.1.8. ‘Which SvelteKit adapter would you like to use?’라는 질문에 static을 선택합니다.
1.1.9. 사용할 package manager를 선택합니다. 저는 npm으로 하겠으며, 이후의 설명도 npm을 기준으로 합니다.
1.1.10. “You’re all set!”라고 뜨면 SvelteKit 프로젝트는 생성이 된 것입니다. 1.1.3번에서 입력한 프로젝트명의 폴더도 생겼을 것입니다. 그러나 우리는 Electron도 써야 하므로 아직 할 것이 더 남았습니다.
1.1.11. 일단 VS Code에서 편하게 작업하기 위해 방금 생성된 프로젝트명의 폴더를 열겠습니다.
1.1.12. 터미널에서 npm install electron-is-dev 명령을 실행합니다.
electron-is-dev는 개발 환경(디버깅)인지 빌드가 완료된 환경인지 확인하는 라이브러리입니다.
1.1.13. npm install -D electron electron-builder concurrently wait-on 명령을 실행합니다.
- -D: devDependencies에 추가하여 이하 패키지들을, 프로젝트 개발할 때만 사용(--save-dev와 동일)
- electron: 웹앱을 PC용 앱으로 만들기 위한 프레임워크
- electron-builder: Electron으로 개발한 앱을 배포하기 위한 빌더
- concurrently: 동시에 여러 명령어를 사용하기 위해 사용
- wait-on: HTTP 자원, PORT, FILE 등이 활성화 될 때까지 기다려 주는 cross platfrom
2. 프로젝트 폴더 구조
프로젝트 폴더 구조는 아래와 같습니다.
- 📁.svelte-kit
- 📁build: SvelteKit→static 빌드 결과물(빌드 시 자동 생성)
- 📁dist: static→electron-builder 빌드 결과물(빌드 시 자동 생성)
- 📁node_modules
- 📁src: Svelte 코드들이 들어 있는 폴더
- 📁static: 정적 파일들이 들어 있는 폴더(예: CSS 코드, 이미지 등)
- 📁buildResources: NSIS 빌드에 필요한 헤더/환영 이미지 등([electron-builder]NSIS(.exe)로 빌드하기 참고)
- favicon.png: 프로그램 아이콘(프로젝트 생성 시 기본 제공되는 파비콘을 쓰지 마시고, 256x256 이상의 해상도로 제작하세요.)
- shared.css: 공통된 CSS(6번 단락 참고)
- .gitignore: Git 사용 시 제외할 파일 목록
- .npmrc
- electron.cts: Electron 메인 코드(7번 단락에서 만듦.)
- package-lock.json
- package.json: 패키지 전반에 대한 설정 파일(4번 단락에서 설명)
- README.md
- svelte.config.js: Svelte 설정과 관련된 코드(3번 단락에서 설명)
- tsconfig.json
- vite.config.ts
3. svelte.config.js
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://svelte.dev/docs/kit/integrations
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
// default options are shown. On some platforms
// these options are set automatically — see below
pages: 'build',
assets: 'build',
fallback: undefined,
precompress: false,
strict: true
})
}
};
export default config;
11행 부근 adpater 부분을 위의 11~17행과 같이 수정합니다.
13~14행은 페이지들과 assets를 build 폴더로 내보낸다는 뜻입니다.
4. package.json
{
"name": "electron-sveltekit",
"private": true,
"version": "0.0.1",
"type": "module",
"main": "electron.cjs",
"scripts": {
"dev": "tsc ./electron.cts --esModuleInterop true && concurrently \"vite dev\" \"wait-on http://localhost:5173 && electron .\"",
"build": "tsc ./electron.cts --esModuleInterop true && vite build && electron-builder",
"preview": "vite preview",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
},
"build": {
"appId": "com.sooseongcom.practice",
"productName": "Electron-SvelteKit 연습",
"win": {
"target": [
{
"target": "nsis",
"arch": [
"x64",
"ia32"
]
}
],
"icon": "build/favicon.png"
},
"nsis": {
//...(중략)
},
"files": [
"build/**/*",
"electron.cjs"
],
"directories": {
"output": "dist"
}
},
//...(devDependencies와 dependencies는 그대로 두세요.)
}
[Line 6 main]
“main”: “electron.cjs”
main을 “electron.cjs”로 설정합니다.
확장명이 ts 아니고, js 아니고, cts 아니고, cjs입니다. 주의하세요!
[Line 8 dev]
npm run dev 명령어를 사용하면 실행할 것을 설정하는 부분입니다.
먼저 ./electron.cts 파일을 컴파일합니다. 이때 --esModuleInterop을 true로 설정하여 ES Module 대신 commonjs 모듈을 사용하도록 합니다.
그러고 나서 concurrently를 사용하여 \“vite dev\”와 \“wait-on http://localhost:5173 && electron .\”이 동시에 실행되도록 합니다.
[Line 9 build]
“build”: “tsc ./electron.cts –esModuleInterop true && vite build && electron-builder“
npm run build 명령어를 사용하면 먼저 ./electron.cts 파일을 컴파일하되 --esModuleInterop을 true로 설정합니다. 그러면 ./electron.cjs가 생성됩니다. 그러고 나서 SvelteKit 빌드를 합니다. 그러면 build 폴더가 생성됩니다. 그러고 나서 electron-builder로 빌드합니다. 그러면 dist 폴더가 생성됩니다.
[Line 28 icon]
“icon”: “build/favicon.png“
예제에서는 위와 같은 아이콘을 만들어 static/favicon.png로 저장했습니다. 그러면 빌드 시 build/favicon.png로 생성됩니다. electron-builder를 사용하기 위하여 아이콘은 256x256 이상의 크기 PNG 파일로 만들어야 합니다. 참고로 SvelteKit에서 기본 제공되는 파비콘은 크기가 작아서 Electron 빌드 시 오류가 발생됩니다.
[Line 16~32 build(중략 부분 포함)]
빌드 설정에 관한 부분은 과거에 설명한 적이 있으므로 해당 글 링크로 설명을 대신합니다.
[electron-builder]NSIS(.exe)로 빌드하기
[Line 33~36 files]
33행) “files”: [
34행) “build/**/*”,
35행) “electron.cjs”
36행) ],
뒤에서 살펴보겠지만 SvelteKit 빌드 결과물은 build 폴더에 저장됩니다. 그러나 electron-builder는 기본적으로 build 폴더 안의 파일을 처리하지 않으므로 files 항목을 설정해야 합니다.
“build/**/*“는 build 폴더 안의 모든 파일을 빌드 대상으로 삼겠다는 뜻입니다.
“electron.cjs”는 Electron 메인 코드이므로 이것도 포함해야 앱이 정상 작동합니다.
[Line 38 output]
“output”: “dist”
최종 설치 파일(.exe)을 생성할 경로를 설정합니다. 저는 dist로 했습니다.
5. src/app.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
<link rel="stylesheet" href="%sveltekit.assets%/shared.css" />
<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'"/>
<meta http-equiv="X-Content-Security-Policy" content="default-src 'self'; script-src 'self'"/>
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
[Line 8]
static/shared.css를 사용하기 위해서 추가합니다.
[Line 10~12]
Electron Security Warning 해결하기 위해서 추가합니다.
6. static/shared.css 예제
이 파일은 자율입니다. 저를 따라하실 분들을 위해서 예제 코드를 올려 드립니다.
h1{background-color: #aaa;}
nav{
background: #e0e6eb;
z-index: 2;
border-radius: 0.5em;
gap: 1em;
margin: 0 0 1em;
padding: 1em;
display: flex;
position: relative
}
nav a{text-decoration: none}
nav a[aria-current=true]{border-bottom: 2px solid}
7. electron.cts
.cts 파일 확장자는 TypeScript에서 commonjs 모듈 시스템을 사용함을 명시적으로 나타내는 파일 확장명입니다. 아이콘 경로를 설정하기 위해서 node:path를 사용하는데요, 이를 위해서 확장명을 .ts가 아닌 .cts로 해야 합니다.
import { app, BrowserWindow } from 'electron'
import isDev from 'electron-is-dev';
import path from 'node:path';
let iconpath = isDev ? path.join(__dirname, '/static/favicon.png') : path.join(__dirname, '/build/favicon.png');
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600,
icon: iconpath
});
if(isDev) win.loadURL('http://localhost:5173');
else win.loadFile('build/index.html');
}
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
})
})
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
})
[Line 2 isDev]
import isDev from ‘electron-is-dev’;
electron-is-dev에서 isDev를 불러옵니다. isDev는 프로그램 실행 상태가 개발 중(디버깅)인지 빌드 후인지 확인합니다.
[Line 3 path]
import path from ‘node:path’
Node.js의 path 모듈을 불러옵니다.
[Line 5 iconpath]
let iconpath = isDev ? path.join(__dirname, ‘/static/favicon.png’) : path.join(__dirname, ‘/build/favicon.png’);
path.join method부터 보겠습니다. path.join은 플랫폼(Windows/Linux/macOS)별로 구분자를 사용하여 경로를 정규화 해 줍니다. path.join의 2번째 인자에 아이콘 파일 경로를 넣습니다.
isDev 값에 따라 iconpath에 다른 경로를 저장하도록 삼항 연산자를 사용하였습니다.
isDev==true일 때는 iconpath에 path.join(__dirname, ‘/static/favicon.png’)를 대입합니다.
isDev==false일 때는 iconpath에 path.join(__dirname, ‘/build/favicon.png’)를 대입합니다.
[Line 7~16 createWindow 함수 정의]
앱 실행 시 창을 만드는 것에 대한 내용입니다.
[Line 9 width]
width: 800
창 가로 길이를 입력합니다.
[Line 10 height]
height: 600
창 세로 길이를 입력합니다.
[Line 11 icon]
icon: iconpath
아이콘 경로에 iconpath를 지정합니다.
[Line 14]
if(isDev) win.loadURL(‘http://localhost:5173’);
isDev==true일 때, 즉 디버깅을 하면 http://localhost:5173을 띄웁니다.
[Line 15]
else win.loadFile(‘build/index.html’);
isDev==false일 때, 즉 빌드 후 앱을 설치한 다음 실행하면 build/index.html을 띄웁니다.
[Line 18~19]
앱을 실행하면 createWindow 함수를 호출하여 창을 생성합니다.
[Line 21~25(macOS를 위한 코드)]
macOS는 창이 모두 닫혀도 앱이 종료되지 않습니다. 따라서 창이 1개도 없을 때 createWindow 함수를 호출하는 코드가 필요합니다.
[Line 28~32(Windows와 Linux를 위한 코드)]
창이 모두 닫히면 앱을 종료합니다.
8. src/routes/+layout.ts
export const prerender = true;
이 코드를 작성하여 src/routes/+layout.ts를 저장하세요. 이는 빌드할 때 모든 파일이 prerendering 되도록 합니다.
9. src/lib/href.ts
SvelteKit은 디버깅할 때 서버로 구동됩니다. 따라서 하이퍼링크를 작성할 때 ‘/’를 root로 쓸 수 있습니다. 그러나 빌드 후에 Electron 앱으로 실행할 때는 로컬 파일로 작동하므로 ‘/’를 root로 쓸 수 없습니다. 이 문제를 해결하기 위하여 하이퍼링크 경로를 처리하는 함수를 만들겠습니다.
이 함수가 필요한 페이지 어디서든지 호출할 수 있도록 src/lib/href.ts을 생성하여 코드를 작성합니다.(src/lib 경로 안에 있는 라이브러리는 $lib 환경 변수로 호출 가능)
import {dev} from '$app/environment';
import {base} from '$app/paths';
export function href(src: string): string{
let result: string;
if(dev){
result=src;
}
else if(src=="/"){
result=base+"/index.html";
}
else{
result=base+src+".html";
}
return result;
}
[Line 1 {dev}]
import {dev} from ‘$app/environment’;
{dev}는 SvelteKit에서 디버깅 중인지 아닌지 확인하는 라이브러리입니다. electron-is-dev와 다른 별개의 라이브러리입니다.
[Line 2 {base}]
import {base} from ‘$app/paths’;
base 링크를 생성해 주는 라이브러리입니다.
[Line 4]
export function href(src: string): string{
src는 string으로 선언하고, href 함수의 반환 역시 string으로 정의합니다.
함수 호출 시 src “/path“의 형태로 넣습니다.
[Line 5]
let result: string;
result string을 만듭니다.
[Line 7~9]
dev==true이면(디버깅 중이면) result=src;를 실행합니다.
[Line 10~12]
그렇지 않고 src==”/”이면 result=base+”/index.html”;을 실행합니다.
[Line 13~15]
그렇지 않으면 result=base+src+”.html”;을 실행합니다.
[Line 17]
return result;
result를 반환합니다.
10. src/routes/+layout.svelte
<script lang="ts">
import {href} from '$lib/href';
let {children}=$props();
</script>
<nav>
<a href="{href("/")}">home</a>
<a href="{href("/about")}">about</a>
</nav>
{@render children()}
[Line 2 href]
import {href} from ‘$lib/href’;
src/lib/href.ts에 작성한 href를 import합니다. src/lib 안에 있는 파일에서 함수를 불러올 때는 $lib 환경변수를 사용할 수 있습니다. from ‘$lib/href‘와 같이 작성합니다. 이때 .ts를 적지 않도록 주의합니다.
[Line 3 children]
let {children}=$props();
$props Rune으로 {children} 변수를 선언합니다.
[Line 7]
<a href=”{href(”/”)}”>home</a>
a 태그의 href 속성값을 작성할 때 {href(“경로”)}와 같이 작성합니다.
[Line 8]
<a href=”{href(”/about”)}”>about</a>
[Line 11]
{@render children()}
자식 내용이 들어갈 자리에 이 코드를 작성해 줍니다.
11. src/routes/+page.svelte 예제
<script lang="ts">
import {href} from "$lib/href";
</script>
<svelte:head>
<title>Welcome to SvelteKit</title>
</svelte:head>
<p>This is the home page.</p>
<ul>
<li><a href="{href("/about")}">about</a></li>
</ul>
12. src/routes/about/+page.svelte 예제
<h1>about</h1>
<p>this is the about page.</p>
13. 디버깅(npm run dev)
13.1. npm run dev 명령을 실행합니다.
13.2. 기다리면 창이 열립니다. 창의 아이콘과 작업 표시줄의 아이콘 모두 잘 적용된 것을 보실 수 있습니다.
13.3. 디버깅을 종료할 때는 해당 앱의 창을 닫고 npm run dev 명령을 실행한 터미널에서 Ctrl+C를 누른 뒤 y를 입력하고 엔터를 누릅니다.
14. 빌드(npm run build)
14.1. npm run build 명령을 실행합니다.
14.2. 빌드가 끝날 때까지 기다립니다.
14.3. 오류가 발생하지 않았다면 dist 폴더 안에 설치 프로그램이 생성되어 있을 것입니다. 실행하여 설치합니다.
(설치 과정은 생략하겠습니다.)
14.4. 앱을 실행하면 잘 작동되는 것을 보실 수 있을 것입니다. 창의 아이콘과 작업 표시줄의 아이콘, 바로가기 아이콘 모두 잘 적용되었습니다.
14.5. 설치한 앱을 제거하려면 설치된 앱(Windows+X→P)에서 제거하시면 됩니다.
15. 글 마무리
제 글을 읽어 주셔서 감사합니다. 다음에 만나요!
16. 참고 자료
1) JINGMONG. 2022. “Electron 시작하기”, 코딩 일기장. (2025. 07. 29. 방문). https://jingmong.tistory.com/81
2) Svelte contributors. 2025. “$app/paths”, Svlete Docs. (2025. 07. 29. 방문). https://svelte.dev/docs/kit/$app-paths
3) 코딩백구. 2024. “[SVELTE] SvelteKit adapter-static Build 시 에러”, 백구의 코딩찌개. (2025. 07. 29. 방문). https://ai-life4you.com/19
4) 임종필. 2022. “sveltekit 으로 하이브리드앱 만들기”, firsthouse.log. (2025. 07. 29. 방문). https://velog.io/@firsthouse/sveltekit-으로-하이브리드앱-만들기
5) Samuel 외 7명. 2021. “How to differentiate between Svelte dev mode and build mode?”, stackoverflow. (2025. 07. 29. 방문). https://stackoverflow.com/questions/64245188/how-to-differentiate-between-svelte-dev-mode-and-build-mode
6) ghost 외. 2019. “electron-builder app.asar error”, GitHub. (2025. 07. 29. 방문). https://github.com/electron-userland/electron-builder/issues/4429
7) Light9639. 2023. “[Node.js] 🕹️ Concurrently 설치 및 사용법.”, Light Dev. (2025. 07. 29. 방문). https://light9639.tistory.com/entry/concurrently-설치-및-사용법
8) TempoDiValse. 2022. “[Electron] electron-builder의 files에 대해 알아본다”, Tempo Di Valse. (2025. 07. 29. 방문). https://tempodivalse.tistory.com/35