Skip to content

SvelteKit Version โ€‹

HaloLight SvelteKit version is built on SvelteKit 2, featuring Svelte 5 Runes + TypeScript with compile-time optimization and ultimate performance.

Live Preview: https://halolight-svelte.h7ml.cn

GitHub: https://github.com/halolight/halolight-svelte

Features โ€‹

  • ๐Ÿ—๏ธ Svelte 5 Runes - New reactivity system ($state/$derived/$effect)
  • โšก Compile-time Optimization - No virtual DOM, minimal runtime overhead
  • ๐ŸŽจ Theme System - 11 skins, dark mode, View Transitions
  • ๐Ÿ” Authentication - Complete login/register/password recovery flow
  • ๐Ÿ“Š Dashboard - Data visualization and business management
  • ๐Ÿ›ก๏ธ Permission Control - RBAC fine-grained access control
  • ๐Ÿ“‘ Multi-tabs - Browser-style tab management
  • โŒ˜ Command Palette - Keyboard shortcuts (โŒ˜K)

Tech Stack โ€‹

TechnologyVersionDescription
SvelteKit2.xSvelte full-stack framework
Svelte5.xCompile-time framework (Runes)
TypeScript5.9Type safety
Tailwind CSS4.xAtomic CSS
shadcn-sveltelatestUI component library
Superforms2.xForm handling
TanStack Query5.xServer state
Mock.js1.xData mocking

Core Features โ€‹

  • Configurable Dashboard - 9 widgets, drag-and-drop layout, responsive
  • Multi-tab Navigation - Browser-style tabs, context menu, state caching
  • Permission System - RBAC control, route guards, permission components
  • Theme System - 11 skins, dark mode, View Transitions
  • Multi-account Switching - Quick account switching, remember login
  • Command Palette - Keyboard shortcuts (โŒ˜K), global search
  • Real-time Notifications - WebSocket push, notification center

Directory Structure โ€‹

halolight-svelte/
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ routes/                    # File-based routing
โ”‚   โ”‚   โ”œโ”€โ”€ (auth)/                # Auth pages
โ”‚   โ”‚   โ””โ”€โ”€ (dashboard)/           # Dashboard pages
โ”‚   โ”œโ”€โ”€ lib/
โ”‚   โ”‚   โ”œโ”€โ”€ components/            # Components
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ ui/               # Base UI components
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ layout/           # Layout components
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ dashboard/        # Dashboard components
โ”‚   โ”‚   โ”œโ”€โ”€ stores/               # State management (Runes)
โ”‚   โ”‚   โ”œโ”€โ”€ utils/                # Utilities
โ”‚   โ”‚   โ”œโ”€โ”€ mock/                 # Mock data
โ”‚   โ”‚   โ””โ”€โ”€ types/                # Type definitions
โ”‚   โ”œโ”€โ”€ hooks.server.ts           # Server hooks
โ”‚   โ””โ”€โ”€ app.css                   # Global styles
โ”œโ”€โ”€ static/                        # Static assets
โ”œโ”€โ”€ svelte.config.js              # Svelte config
โ””โ”€โ”€ package.json

Quick Start โ€‹

Environment Requirements โ€‹

  • Node.js >= 18.0.0
  • pnpm >= 9.x

Installation โ€‹

bash
git clone https://github.com/halolight/halolight-svelte.git
cd halolight-svelte
pnpm install

Environment Variables โ€‹

bash
cp .env.example .env
env
# .env
VITE_API_URL=/api
VITE_MOCK=true
VITE_DEMO_EMAIL=admin@halolight.h7ml.cn
VITE_DEMO_PASSWORD=123456
VITE_SHOW_DEMO_HINT=false
VITE_APP_TITLE=Admin Pro
VITE_BRAND_NAME=Halolight

Start Development โ€‹

bash
pnpm dev

Visit http://localhost:5173

Build for Production โ€‹

bash
pnpm build
pnpm preview

Core Features โ€‹

State Management (Svelte 5 Runes) โ€‹

ts
// lib/stores/auth.ts
import { browser } from '$app/environment';

interface User {
  id: number;
  name: string;
  email: string;
  permissions: string[];
}

class AuthStore {
  user = $state<User | null>(null);
  token = $state<string | null>(null);

  isAuthenticated = $derived(!!this.token && !!this.user);
  permissions = $derived(this.user?.permissions ?? []);

  constructor() {
    if (browser) {
      const saved = localStorage.getItem('auth');
      if (saved) {
        const { user, token } = JSON.parse(saved);
        this.user = user;
        this.token = token;
      }
    }
  }

  async login(credentials: { email: string; password: string }) {
    const response = await fetch('/api/auth/login', {
      method: 'POST',
      body: JSON.stringify(credentials),
      headers: { 'Content-Type': 'application/json' },
    });

    const data = await response.json();
    this.user = data.user;
    this.token = data.token;
    this.persist();
  }

  logout() {
    this.user = null;
    this.token = null;
    localStorage.removeItem('auth');
  }

  hasPermission(permission: string): boolean {
    return this.permissions.some(
      (p) =>
        p === '*' || p === permission || (p.endsWith(':*') && permission.startsWith(p.slice(0, -1)))
    );
  }

  private persist() {
    if (browser) {
      localStorage.setItem(
        'auth',
        JSON.stringify({
          user: this.user,
          token: this.token,
        })
      );
    }
  }
}

export const authStore = new AuthStore();

Data Fetching (Load Functions) โ€‹

ts
// routes/(dashboard)/+layout.ts
import type { LayoutLoad } from './$types';
import { redirect } from '@sveltejs/kit';

export const load: LayoutLoad = async ({ parent, url }) => {
  const { user } = await parent();

  if (!user) {
    throw redirect(302, `/auth/login?redirect=${url.pathname}`);
  }

  return { user };
};
svelte
<!-- routes/(dashboard)/dashboard/+page.svelte -->
<script lang="ts">
  import type { PageData } from './$types';

  let { data }: { data: PageData } = $props();
</script>

<h1>Welcome, {data.user.name}!</h1>

Permission Control โ€‹

svelte
<!-- lib/components/PermissionGuard.svelte -->
<script lang="ts">
  import { authStore } from '$lib/stores/auth';

  interface Props {
    permission: string;
    children: import('svelte').Snippet;
    fallback?: import('svelte').Snippet;
  }

  let { permission, children, fallback }: Props = $props();

  const hasPermission = $derived(authStore.hasPermission(permission));
</script>

{#if hasPermission}
  {@render children()}
{:else if fallback}
  {@render fallback()}
{/if}
svelte
<!-- Usage example -->
<PermissionGuard permission="users:delete">
  {#snippet children()}
    <Button variant="destructive">Delete</Button>
  {/snippet}
  {#snippet fallback()}
    <span class="text-muted-foreground">No Permission</span>
  {/snippet}
</PermissionGuard>

Draggable Dashboard โ€‹

svelte
<script lang="ts">
  import { SvelteSet } from 'svelte/reactivity';
  import GridLayout from '$lib/components/dashboard/GridLayout.svelte';

  // Reactive Set for managing widgets
  let activeWidgets = new SvelteSet(['stats', 'chart', 'recent']);

  const layout = $state([
    { i: 'stats', x: 0, y: 0, w: 4, h: 2 },
    { i: 'chart', x: 4, y: 0, w: 8, h: 4 },
    { i: 'recent', x: 0, y: 2, w: 4, h: 2 },
  ]);

  function onLayoutChange(newLayout: typeof layout) {
    layout.splice(0, layout.length, ...newLayout);
    localStorage.setItem('dashboard-layout', JSON.stringify(newLayout));
  }
</script>

<GridLayout {layout} on:change={onLayoutChange}>
  {#each [...activeWidgets] as widget}
    <div data-grid-item={widget}>
      <Widget type={widget} />
    </div>
  {/each}
</GridLayout>

Theme System โ€‹

Skin Presets โ€‹

Support 11 preset skins, switch via quick settings panel:

SkinPrimary ColorCSS Variable
DefaultPurple--primary: 51.1% 0.262 276.97
BlueBlue--primary: 54.8% 0.243 264.05
EmeraldEmerald--primary: 64.6% 0.178 142.49
OrangeOrange--primary: 68.9% 0.181 40.84
RoseRose--primary: 60.7% 0.234 11.63
TealTeal--primary: 62.8% 0.149 186.07
YellowYellow--primary: 82.3% 0.165 92.14
VioletViolet--primary: 58.9% 0.264 292.85
CyanCyan--primary: 73.2% 0.152 196.85
PinkPink--primary: 70.5% 0.226 340.54
IndigoIndigo--primary: 52.4% 0.218 270.32

CSS Variables (OKLch) โ€‹

css
/* app.css */
@layer base {
  :root {
    --background: 100% 0 0;
    --foreground: 14.9% 0.017 285.75;
    --primary: 51.1% 0.262 276.97;
    --primary-foreground: 98% 0.007 285.89;
    --secondary: 96.1% 0.006 286.32;
    --secondary-foreground: 14.9% 0.017 285.75;
    --muted: 96.1% 0.006 286.32;
    --muted-foreground: 45.5% 0.026 285.82;
    --accent: 96.1% 0.006 286.32;
    --accent-foreground: 14.9% 0.017 285.75;
    --destructive: 61.1% 0.246 29.23;
    --destructive-foreground: 98% 0.007 285.89;
    --border: 92.1% 0.011 286.32;
    --input: 92.1% 0.011 286.32;
    --ring: 51.1% 0.262 276.97;
    --radius: 0.5rem;
  }

  .dark {
    --background: 22.4% 0.015 285.88;
    --foreground: 98% 0.007 285.89;
    --primary: 61.1% 0.262 276.97;
    --primary-foreground: 98% 0.007 285.89;
    /* ... */
  }
}

View Transitions Theme Switching โ€‹

svelte
<script lang="ts">
  function toggleTheme() {
    if (!document.startViewTransition) {
      document.documentElement.classList.toggle('dark');
      return;
    }

    document.startViewTransition(() => {
      document.documentElement.classList.toggle('dark');
    });
  }
</script>

<button onclick={toggleTheme}>Toggle Theme</button>

<style>
  :global(::view-transition-old(root)),
  :global(::view-transition-new(root)) {
    animation-duration: 0.3s;
  }
</style>

Page Routes โ€‹

PathPagePermission
/Home (redirect)Public
/auth/loginLoginPublic
/auth/registerRegisterPublic
/auth/forgot-passwordForgot PasswordPublic
/auth/reset-passwordReset PasswordPublic
/dashboardDashboarddashboard:view
/dashboard/usersUser Managementusers:view
/dashboard/analyticsAnalyticsanalytics:view
/dashboard/calendarCalendarcalendar:view
/dashboard/documentsDocumentsdocuments:view
/dashboard/filesFilesfiles:view
/dashboard/messagesMessagesmessages:view
/dashboard/notificationsNotificationsnotifications:view
/dashboard/settingsSettingssettings:view
/dashboard/profileProfilesettings:view

Common Commands โ€‹

bash
pnpm dev            # Start dev server
pnpm build          # Production build
pnpm preview        # Preview production build
pnpm lint           # Lint code
pnpm lint:fix       # Auto-fix lint issues
pnpm format         # Format code
pnpm check          # Type check (svelte-check)
pnpm test           # Run tests
pnpm test:coverage  # Test coverage
pnpm ci             # Full CI check

Deployment โ€‹

Deploy to Cloudflare Pages

Project is configured with Cloudflare Pages adapter by default:

js
// svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare';

export default {
  kit: {
    adapter: adapter(),
  },
};
bash
pnpm build
# Cloudflare Pages will automatically deploy main branch

Docker โ€‹

bash
docker build -t halolight-svelte .
docker run -p 3000:3000 halolight-svelte

Other Platforms โ€‹

Demo Accounts โ€‹

RoleEmailPassword
Adminadmin@halolight.h7ml.cn123456
Useruser@halolight.h7ml.cn123456

Testing โ€‹

bash
pnpm test           # Run tests (watch mode)
pnpm test:run       # Single run
pnpm test:coverage  # Coverage report
pnpm test:ui        # Vitest UI

Test Examples โ€‹

ts
// tests/auth.test.ts
import { describe, it, expect } from 'vitest';
import { authStore } from '$lib/stores/auth';

describe('AuthStore', () => {
  it('should initialize with null user', () => {
    expect(authStore.user).toBeNull();
    expect(authStore.isAuthenticated).toBe(false);
  });

  it('should authenticate user', async () => {
    await authStore.login({
      email: 'admin@halolight.h7ml.cn',
      password: '123456',
    });

    expect(authStore.isAuthenticated).toBe(true);
    expect(authStore.user?.email).toBe('admin@halolight.h7ml.cn');
  });

  it('should check permissions', () => {
    expect(authStore.hasPermission('users:view')).toBe(true);
    expect(authStore.hasPermission('invalid')).toBe(false);
  });
});

Configuration โ€‹

SvelteKit Configuration โ€‹

js
// svelte.config.js
import adapter from '@sveltejs/adapter-cloudflare';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

export default {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter(),
    alias: {
      $components: 'src/lib/components',
      $stores: 'src/lib/stores',
      $utils: 'src/lib/utils',
    },
  },
};

Vite Configuration โ€‹

ts
// vite.config.ts
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vitest/config';

export default defineConfig({
  plugins: [sveltekit()],
  test: {
    include: ['src/**/*.{test,spec}.{js,ts}'],
    environment: 'jsdom',
  },
});

CI/CD โ€‹

Complete GitHub Actions CI workflow configured:

yaml
# .github/workflows/ci.yml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm lint
      - run: pnpm format:check

  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm check

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm test:coverage
      - uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm build

  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: pnpm
      - run: pnpm audit --audit-level=high

Advanced Features โ€‹

Reactive Collections (SvelteSet/SvelteMap) โ€‹

svelte
<script lang="ts">
  import { SvelteSet, SvelteMap } from 'svelte/reactivity';

  // Reactive Set
  let selectedIds = new SvelteSet<string>();

  function toggleSelection(id: string) {
    if (selectedIds.has(id)) {
      selectedIds.delete(id);
    } else {
      selectedIds.add(id);
    }
  }

  // Reactive Map
  let itemStatus = new SvelteMap<string, 'pending' | 'done'>();

  function markDone(id: string) {
    itemStatus.set(id, 'done');
  }
</script>

<p>Selected: {selectedIds.size}</p>

Server Hooks โ€‹

ts
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';

export const handle: Handle = async ({ event, resolve }) => {
  const token = event.cookies.get('token');

  if (token) {
    // Validate token and set user info
    event.locals.user = await validateToken(token);
  }

  // Route protection
  if (event.url.pathname.startsWith('/dashboard')) {
    if (!event.locals.user) {
      return new Response(null, {
        status: 302,
        headers: { Location: '/auth/login' },
      });
    }
  }

  return resolve(event);
};

Performance Optimization โ€‹

Lazy Load Components โ€‹

svelte
<script lang="ts">
  const HeavyComponent = $lazy(() => import('$lib/components/Heavy.svelte'));
</script>

{#await HeavyComponent}
  <div>Loading...</div>
{:then component}
  <svelte:component this={component} />
{/await}

Preloading โ€‹

svelte
<script lang="ts">
  import { preloadData } from '$app/navigation';

  function handleMouseEnter() {
    preloadData('/dashboard/analytics');
  }
</script>

<a href="/dashboard/analytics" onmouseenter={handleMouseEnter}>
  Analytics
</a>

Image Optimization โ€‹

svelte
<script lang="ts">
  import { onMount } from 'svelte';

  let visible = $state(false);

  onMount(() => {
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        visible = true;
        observer.disconnect();
      }
    });

    observer.observe(element);
  });
</script>

{#if visible}
  <img src="/large-image.jpg" alt="Optimized image" />
{:else}
  <div class="placeholder" />
{/if}

FAQ โ€‹

Q: How to use TanStack Query in SvelteKit? โ€‹

A: SvelteKit recommends using built-in Load functions for data loading, but you can also combine TanStack Query:

svelte
<script lang="ts">
  import { createQuery } from '@tanstack/svelte-query';

  const query = createQuery({
    queryKey: ['users'],
    queryFn: () => fetch('/api/users').then(r => r.json()),
  });
</script>

{#if $query.isLoading}
  <p>Loading...</p>
{:else if $query.error}
  <p>Error: {$query.error.message}</p>
{:else if $query.data}
  <ul>
    {#each $query.data as user}
      <li>{user.name}</li>
    {/each}
  </ul>
{/if}

Q: How to implement form validation? โ€‹

A: Recommended using Superforms + Zod:

ts
// routes/users/create/+page.server.ts
import { superValidate } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { z } from 'zod';

const schema = z.object({
  name: z.string().min(2),
  email: z.string().email(),
});

export const actions = {
  default: async ({ request }) => {
    const form = await superValidate(request, zod(schema));

    if (!form.valid) {
      return fail(400, { form });
    }

    // Process form data
    return { form };
  },
};

Q: How to deploy to Vercel? โ€‹

A: Switch to Vercel adapter:

bash
pnpm add -D @sveltejs/adapter-vercel
js
// svelte.config.js
import adapter from '@sveltejs/adapter-vercel';

export default {
  kit: {
    adapter: adapter(),
  },
};

Comparison with Other Versions โ€‹

FeatureSvelteKitNext.jsVue
SSR/SSGโœ…โœ…โœ… (Nuxt)
State ManagementSvelte 5 RunesZustandPinia
RoutingFile-basedApp RouterVue Router
Build ToolViteTurbopackVite
RuntimeNo Virtual DOMVirtual DOMVirtual DOM
FormsSuperformsReact Hook FormVeeValidate
Component Libraryshadcn-svelteshadcn/uishadcn-vue