Skip to content

Svelte 5, SvelteKit, and pnpm Development Setup

This document provides guidance on setting up and working with Svelte 5, SvelteKit, and pnpm in the Meta Agent Platform project.

Overview

The Meta Agent Platform frontend is built with Svelte 5 and SvelteKit, leveraging Svelte's fine-grained reactivity system and runes for efficient state management, along with SvelteKit's routing, server-side rendering, and API capabilities. We use pnpm as our package manager for its performance benefits and disk space efficiency.

pnpm Setup

Installation

Install pnpm globally:

# Using npm
npm install -g pnpm

# Using curl for Unix systems
curl -fsSL https://get.pnpm.io/install.sh | sh -

# Using PowerShell for Windows
iwr https://get.pnpm.io/install.ps1 -useb | iex

Key pnpm Commands

# Install all dependencies
pnpm install

# Add a dependency
pnpm add <package-name>

# Add a dev dependency
pnpm add -D <package-name>

# Run a script from package.json
pnpm run <script-name>

# Update dependencies
pnpm update

# Run the development server
pnpm dev

pnpm Workspace Configuration

For our monorepo structure, we use pnpm workspaces. The configuration is in pnpm-workspace.yaml:

packages:
  - 'packages/*'
  - 'apps/*'

This allows us to manage multiple packages within a single repository, sharing dependencies and enabling cross-package development.

Svelte 5 Development

Project Structure

Our SvelteKit project follows this structure:

apps/frontend/
├── src/
│   ├── lib/
│   │   ├── components/
│   │   ├── stores/
│   │   └── utils/
│   ├── routes/
│   │   ├── +layout.svelte
│   │   ├── +layout.server.ts
│   │   ├── +page.svelte
│   │   ├── +page.server.ts
│   │   └── [...]/
│   │       ├── +page.svelte
│   │       └── +page.server.ts
│   ├── params/
│   ├── hooks.server.ts
│   └── app.html
├── static/
├── svelte.config.js
├── tsconfig.json
└── vite.config.ts

SvelteKit Routing

SvelteKit uses a file-based routing system:

  • src/routes/+page.svelte - The home page
  • src/routes/about/+page.svelte - The /about page
  • src/routes/[slug]/+page.svelte - Dynamic route with a parameter
  • src/routes/+layout.svelte - Layout that wraps all pages
  • src/routes/+page.server.ts - Server-side logic for a page
  • src/routes/api/+server.ts - API endpoint

Using Runes

Svelte 5 introduces runes for state management. Here are the key runes we use:

$state

For reactive component state:

<script>
  let count = $state(0);

  function increment() {
    count++;
  }
</script>

<button onclick={increment}>
  Count: {count}
</button>

$derived

For computed values:

<script>
  let count = $state(0);
  let doubled = $derived(count * 2);
</script>

<p>{count} doubled is {doubled}</p>

$effect

For side effects:

<script>
  let count = $state(0);

  $effect(() => {
    console.log(`Count changed to ${count}`);
    // Update external libraries, APIs, etc.
  });
</script>

Data Loading with SvelteKit

SvelteKit provides a structured way to load data for pages:

// src/routes/workflows/[id]/+page.server.ts
export async function load({ params, fetch }) {
  const response = await fetch(`/api/workflows/${params.id}`);
  const workflow = await response.json();

  return { workflow };
}
<!-- src/routes/workflows/[id]/+page.svelte -->
<script>
  let { workflow } = $props();
</script>

<h1>{workflow.name}</h1>

API Routes with SvelteKit

Create API endpoints using SvelteKit's server routes:

// src/routes/api/workflows/+server.ts
import { json } from '@sveltejs/kit';

export async function GET({ url }) {
  const workflows = await db.getWorkflows();
  return json(workflows);
}

export async function POST({ request }) {
  const data = await request.json();
  const newWorkflow = await db.createWorkflow(data);
  return json(newWorkflow, { status: 201 });
}

Form Actions with SvelteKit

Handle form submissions with progressive enhancement:

// src/routes/login/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';

export const actions = {
  default: async ({ request, cookies }) => {
    const data = await request.formData();
    const email = data.get('email');
    const password = data.get('password');

    if (!email || !password) {
      return fail(400, { email, missing: true });
    }

    const user = await login(email, password);
    if (!user) {
      return fail(400, { email, invalid: true });
    }

    cookies.set('session', user.token, { path: '/' });
    throw redirect(303, '/');
  }
};
<!-- src/routes/login/+page.svelte -->
<script>
  let { form } = $props();
</script>

<form method="POST">
  <input name="email" value={form?.email || ''}>
  <input name="password" type="password">
  <button>Log in</button>

  {#if form?.missing}<p>Missing fields</p>{/if}
  {#if form?.invalid}<p>Invalid credentials</p>{/if}
</form>

Working with Svelte Flow

Svelte Flow is our visual workflow builder. Basic usage:

<script>
  import { SvelteFlow, Background, Controls } from 'svelte-flow';
  import { writable } from 'svelte/store';

  const nodes = $state([
    {
      id: '1',
      type: 'input',
      data: { label: 'Input Node' },
      position: { x: 250, y: 25 }
    }
  ]);

  const edges = $state([]);
</script>

<div style="height: 800px; width: 100%;">
  <SvelteFlow {nodes} {edges}>
    <Background />
    <Controls />
  </SvelteFlow>
</div>

Using shadcn-svelte

shadcn-svelte provides customizable UI components:

<script>
  import { Button } from "$lib/components/ui/button";
  import { Input } from "$lib/components/ui/input";
</script>

<div>
  <Input placeholder="Enter your name" />
  <Button>Submit</Button>
</div>

Development Workflow

Creating a New SvelteKit Project

# Create a new SvelteKit project
pnpm create svelte@latest my-app

# Select options:
# - Skeleton project
# - TypeScript
# - ESLint, Prettier, Vitest, etc.

# Navigate to the project
cd my-app

# Install dependencies
pnpm install

Starting the Development Server

# Navigate to the frontend app
cd apps/frontend

# Install dependencies
pnpm install

# Start the development server
pnpm dev

Building for Production

# Build the frontend
pnpm build

# Preview the production build
pnpm preview

Running Tests

# Run unit tests
pnpm test

# Run tests in watch mode
pnpm test:watch

Best Practices

State Management

  1. Use Runes for Component State: Prefer Svelte 5's runes ($state, $derived, $effect) over external state management libraries.

  2. Shared State: For state shared between components, use stores in $lib/stores/.

  3. API State: Use svelte-query for API data fetching and caching.

  4. Server State: Use SvelteKit's load functions for server-side data loading.

  5. Form State: Use SvelteKit's form actions with Superforms for form handling.

Component Design

  1. Atomic Design: Follow atomic design principles (atoms, molecules, organisms, templates, pages).

  2. Props Typing: Always type your component props:

<script>
  let { title, description = 'Default description' } = $props<{
    title: string;
    description?: string;
  }>();
</script>
  1. Component Documentation: Document components with JSDoc comments.

  2. Layout Structure: Use SvelteKit layouts for shared UI elements.

  3. Route Organization: Group related routes in directories.

Performance Considerations

  1. Avoid Unnecessary Reactivity: Don't make values reactive if they don't need to be.

  2. Use $derived for Computed Values: Instead of computing values in the template or in effects.

  3. Lazy Loading: Use dynamic imports for large components that aren't immediately needed.

Troubleshooting

Common Issues

  1. pnpm Resolution Issues: If you encounter dependency resolution issues, try:

    pnpm store prune
    pnpm install --force
    

  2. TypeScript Errors: Ensure your tsconfig.json is properly configured for Svelte 5.

  3. Svelte Flow Node Types: If you encounter type issues with Svelte Flow nodes, check the type definitions and ensure you're using the correct properties.

Debugging

  1. Svelte DevTools: Install the Svelte DevTools browser extension for debugging.

  2. Vite Debugging: Use the --debug flag with Vite for more verbose output:

    pnpm dev --debug
    

Deployment

SvelteKit provides adapters for various deployment platforms:

# Install the Cloudflare Pages adapter
pnpm add -D @sveltejs/adapter-cloudflare

Update svelte.config.js:

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

/** @type {import('@sveltejs/kit').Config} */
const config = {
  preprocess: vitePreprocess(),
  kit: {
    adapter: adapter()
  }
};

export default config;

Build for production:

pnpm build

Resources

Migration Notes

From React to Svelte

If you're familiar with React, here are some key differences when working with Svelte:

  1. No Virtual DOM: Svelte compiles your components to efficient imperative code.

  2. Reactivity vs. Hooks: Svelte's reactivity system is more intuitive than React hooks.

  3. Scoped CSS: CSS in Svelte components is scoped by default.

  4. Less Boilerplate: Svelte generally requires less code than equivalent React components.

  5. Transitions and Animations: Built-in transition and animation systems.

From Next.js to SvelteKit

If you're familiar with Next.js, here are some key differences when working with SvelteKit:

  1. File Structure: SvelteKit uses +page.svelte and +layout.svelte instead of page.tsx and layout.tsx

  2. Data Loading: SvelteKit uses load functions instead of getServerSideProps or React Server Components

  3. API Routes: SvelteKit uses +server.ts files with HTTP method exports instead of API handlers

  4. Form Handling: SvelteKit has built-in form actions with progressive enhancement

  5. Middleware: SvelteKit uses hooks.server.ts instead of middleware files


Last updated: 2025-04-18