🎯 Ejemplos recomendados
Balanced sample collections from various categories for you to explore
Ejemplos SvelteKit
Ejemplos de framework SvelteKit - La forma más rápida de construir apps Svelte con SSR, routing y características web modernas
💻 SvelteKit Básicos y Componentes svelte
🟢 simple
⭐⭐
Conceptos básicos de SvelteKit incluyendo componentes, páginas, routing y funcionalidad básica
⏱️ 25 min
🏷️ sveltekit, svelte, frontend, components, routing
Prerequisites:
HTML/CSS basics, JavaScript ES6+, Basic web concepts
<!-- SvelteKit Basics and Components -->
<!-- 1. Basic SvelteKit App Structure -->
<!--
my-sveltekit-app/
├── src/
│ ├── lib/
│ │ └── components/
│ │ ├── Header.svelte
│ │ └── Footer.svelte
│ ├── routes/
│ │ ├── +page.svelte # Home page
│ │ ├── about/
│ │ │ └── +page.svelte # About page
│ │ ├── api/
│ │ │ └── users/
│ │ │ └── +server.js # API route
│ │ └── blog/
│ │ ├── [slug]/
│ │ │ └── +page.svelte # Dynamic route
├── static/ # Static assets
├── svelte.config.js
└── package.json
-->
<!-- 2. Main Layout (src/routes/+layout.svelte) -->
<script>
import '../app.css';
import Header from '$lib/components/Header.svelte';
import Footer from '$lib/components/Footer.svelte';
import { page } from '$app/stores';
</script>
<div class="app">
<Header />
<main>
<slot />
</main>
<Footer />
</div>
<style>
.app {
min-height: 100vh;
display: flex;
flex-direction: column;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
main {
flex: 1;
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
width: 100%;
}
</style>
<!-- 3. Home Page (src/routes/+page.svelte) -->
<script>
import { onMount } from 'svelte';
import Link from 'svelte-routing';
import Counter from '$lib/components/Counter.svelte';
let featuredPosts = [];
let loading = true;
onMount(async () => {
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 1000));
featuredPosts = [
{
id: 1,
title: 'Getting Started with SvelteKit',
excerpt: 'Learn the basics of SvelteKit and how to build your first application.',
date: '2024-01-15',
tags: ['SvelteKit', 'Tutorial', 'Frontend']
},
{
id: 2,
title: 'Server-Side Rendering with SvelteKit',
excerpt: 'Understanding SSR and how it improves performance and SEO.',
date: '2024-01-10',
tags: ['SSR', 'Performance', 'SEO']
},
{
id: 3,
title: 'Building API Routes in SvelteKit',
excerpt: 'Create powerful backend APIs using SvelteKit server routes.',
date: '2024-01-05',
tags: ['API', 'Backend', 'Full-stack']
}
];
loading = false;
});
</script>
<svelte:head>
<title>Welcome to SvelteKit</title>
<meta name="description" content="Get started with SvelteKit - the fastest way to build web apps" />
</svelte:head>
<section class="hero">
<h1>Welcome to SvelteKit</h1>
<p>The fastest way to build Svelte apps with SSR, routing, and modern web features</p>
<div class="hero-actions">
<a href="/about" class="btn btn-primary">Learn More</a>
<a href="/docs" class="btn btn-secondary">Documentation</a>
</div>
</section>
<section class="features">
<h2>Why SvelteKit?</h2>
<div class="features-grid">
<div class="feature-card">
<h3>⚡ Lightning Fast</h3>
<p>Compile-time optimizations make your apps incredibly fast and small</p>
</div>
<div class="feature-card">
<h3>🔄 Server-Side Rendering</h3>
<p>Built-in SSR for better performance and SEO out of the box</p>
</div>
<div class="feature-card">
<h3>🎯 File-based Routing</h3>
<p>Intuitive routing system based on your file structure</p>
</div>
<div class="feature-card">
<h3>📱 Progressive Enhancement</h3>
<p>Apps work everywhere, with or without JavaScript</p>
</div>
</div>
</section>
<section class="interactive">
<h2>Try Svelte Reactivity</h2>
<Counter />
</section>
<section class="blog">
<h2>Latest Posts</h2>
{#if loading}
<div class="loading">Loading posts...</div>
{:else if featuredPosts.length > 0}
<div class="posts-grid">
{#each featuredPosts as post (post.id)}
<article class="post-card">
<a href="/blog/{post.id}">
<div class="post-meta">
<time datetime={post.date}>
{new Date(post.date).toLocaleDateString()}
</time>
</div>
<h3>{post.title}</h3>
<p>{post.excerpt}</p>
<div class="post-tags">
{#each post.tags as tag}
<span class="tag">{tag}</span>
{/each}
</div>
</a>
</article>
{/each}
</div>
{:else}
<p>No posts found.</p>
{/if}
</section>
<style>
.hero {
text-align: center;
padding: 4rem 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
margin: -2rem -2rem 2rem -2rem;
}
.hero h1 {
font-size: 3rem;
margin-bottom: 1rem;
font-weight: 700;
}
.hero p {
font-size: 1.2rem;
margin-bottom: 2rem;
opacity: 0.9;
}
.hero-actions {
display: flex;
gap: 1rem;
justify-content: center;
flex-wrap: wrap;
}
.btn {
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
text-decoration: none;
font-weight: 500;
transition: all 0.3s ease;
display: inline-block;
}
.btn-primary {
background-color: white;
color: #667eea;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.btn-secondary {
background-color: transparent;
color: white;
border: 2px solid white;
}
.btn-secondary:hover {
background-color: white;
color: #667eea;
}
.features {
margin: 4rem 0;
}
.features h2 {
text-align: center;
margin-bottom: 3rem;
font-size: 2rem;
color: #333;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 2rem;
}
.feature-card {
text-align: center;
padding: 2rem;
background: #f8f9fa;
border-radius: 1rem;
transition: transform 0.3s ease;
}
.feature-card:hover {
transform: translateY(-5px);
}
.feature-card h3 {
margin-bottom: 1rem;
font-size: 1.5rem;
color: #333;
}
.interactive {
margin: 4rem 0;
text-align: center;
padding: 2rem;
background: #f0f4ff;
border-radius: 1rem;
}
.interactive h2 {
margin-bottom: 2rem;
color: #333;
}
.blog {
margin: 4rem 0;
}
.blog h2 {
margin-bottom: 2rem;
font-size: 2rem;
color: #333;
}
.loading {
text-align: center;
padding: 2rem;
font-size: 1.1rem;
color: #666;
}
.posts-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.post-card {
background: white;
border-radius: 0.75rem;
overflow: hidden;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.post-card:hover {
transform: translateY(-5px);
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
}
.post-card a {
display: block;
padding: 1.5rem;
text-decoration: none;
color: inherit;
}
.post-meta {
margin-bottom: 1rem;
color: #666;
font-size: 0.9rem;
}
.post-card h3 {
margin-bottom: 0.75rem;
color: #333;
font-size: 1.25rem;
line-height: 1.3;
}
.post-card p {
color: #666;
line-height: 1.6;
margin-bottom: 1rem;
}
.post-tags {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.tag {
background: #e3f2fd;
color: #1976d2;
padding: 0.25rem 0.75rem;
border-radius: 1rem;
font-size: 0.8rem;
font-weight: 500;
}
@media (max-width: 768px) {
.hero {
padding: 2rem 1rem;
margin: -1rem -1rem 1rem -1rem;
}
.hero h1 {
font-size: 2rem;
}
.hero-actions {
flex-direction: column;
align-items: center;
}
.features-grid,
.posts-grid {
grid-template-columns: 1fr;
}
}
</style>
<!-- 4. Reusable Counter Component (src/lib/components/Counter.svelte) -->
<script>
let count = 0;
function increment() {
count += 1;
}
function decrement() {
count -= 1;
}
function reset() {
count = 0;
}
</script>
<div class="counter">
<h3>Interactive Counter: {count}</h3>
<div class="counter-controls">
<button on:click={decrement} class="btn btn-decrement">−</button>
<span class="count-display">{count}</span>
<button on:click={increment} class="btn btn-increment">+</button>
</div>
<button on:click={reset} class="btn btn-reset">Reset</button>
<p>
This is a simple Svelte component demonstrating reactivity.
Click the buttons to see the count update in real-time!
</p>
</div>
<style>
.counter {
text-align: center;
padding: 2rem;
background: white;
border-radius: 1rem;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
max-width: 400px;
margin: 0 auto;
}
.counter h3 {
margin-bottom: 1.5rem;
color: #333;
}
.counter-controls {
display: flex;
align-items: center;
justify-content: center;
gap: 1rem;
margin-bottom: 1rem;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 0.5rem;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-increment {
background-color: #4caf50;
color: white;
}
.btn-increment:hover {
background-color: #45a049;
}
.btn-decrement {
background-color: #f44336;
color: white;
}
.btn-decrement:hover {
background-color: #da190b;
}
.btn-reset {
background-color: #ff9800;
color: white;
}
.btn-reset:hover {
background-color: #e68900;
}
.count-display {
font-size: 1.5rem;
font-weight: bold;
min-width: 3rem;
text-align: center;
color: #333;
}
</style>
<!-- 5. Header Component (src/lib/components/Header.svelte) -->
<script>
import { page } from '$app/stores';
</script>
<header class="header">
<nav class="nav">
<div class="nav-brand">
<a href="/" class="logo">SvelteKit</a>
</div>
<ul class="nav-links">
<li>
<a href="/" class:active={$page.url.pathname === '/'}>
Home
</a>
</li>
<li>
<a href="/about" class:active={$page.url.pathname === '/about'}>
About
</a>
</li>
<li>
<a href="/blog" class:active={$page.url.pathname.startsWith('/blog')}>
Blog
</a>
</li>
<li>
<a href="/api/users" class:active={$page.url.pathname === '/api/users'}>
API
</a>
</li>
</ul>
<div class="nav-toggle">
<input type="checkbox" id="nav-checkbox" class="nav-checkbox">
<label for="nav-checkbox" class="nav-toggle-label">
<span></span>
</label>
</div>
</nav>
</header>
<style>
.header {
background-color: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
position: sticky;
top: 0;
z-index: 1000;
}
.nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1rem 2rem;
max-width: 1200px;
margin: 0 auto;
}
.nav-brand .logo {
font-size: 1.5rem;
font-weight: bold;
color: #ff3e00;
text-decoration: none;
transition: color 0.3s ease;
}
.nav-brand .logo:hover {
color: #e63900;
}
.nav-links {
display: flex;
list-style: none;
margin: 0;
padding: 0;
gap: 2rem;
}
.nav-links a {
color: #333;
text-decoration: none;
font-weight: 500;
padding: 0.5rem 1rem;
border-radius: 0.5rem;
transition: all 0.3s ease;
}
.nav-links a:hover {
color: #ff3e00;
background-color: #fff3f0;
}
.nav-links a.active {
color: #ff3e00;
background-color: #fff3f0;
}
.nav-checkbox {
display: none;
}
.nav-toggle-label {
display: none;
flex-direction: column;
cursor: pointer;
}
.nav-toggle-label span {
width: 25px;
height: 3px;
background-color: #333;
margin: 3px 0;
transition: 0.3s;
}
@media (max-width: 768px) {
.nav-links {
position: fixed;
top: 0;
right: -100%;
width: 80%;
max-width: 300px;
height: 100vh;
background: white;
flex-direction: column;
justify-content: center;
align-items: center;
transition: right 0.3s ease;
box-shadow: -2px 0 5px rgba(0,0,0,0.1);
gap: 0;
}
.nav-links li {
width: 100%;
text-align: center;
padding: 1rem 0;
}
.nav-links a {
display: block;
padding: 1rem;
width: 100%;
}
.nav-toggle-label {
display: flex;
}
.nav-checkbox:checked ~ .nav-links {
right: 0;
}
.nav-checkbox:checked ~ .nav-toggle-label span:nth-child(1) {
transform: rotate(-45deg) translate(-5px, 6px);
}
.nav-checkbox:checked ~ .nav-toggle-label span:nth-child(2) {
opacity: 0;
}
.nav-checkbox:checked ~ .nav-toggle-label span:nth-child(3) {
transform: rotate(45deg) translate(-5px, -6px);
}
}
</style>
<!-- 6. About Page (src/routes/about/+page.svelte) -->
<script>
import { onMount } from 'svelte';
let team = [];
let loading = true;
let stats = {
projects: 0,
clients: 0,
years: 0,
coffee: 0
};
onMount(async () => {
// Simulate API calls
await Promise.all([
loadTeam(),
animateStats()
]);
loading = false;
});
async function loadTeam() {
await new Promise(resolve => setTimeout(resolve, 800));
team = [
{
id: 1,
name: 'Sarah Chen',
role: 'Lead Developer',
bio: 'Full-stack developer with expertise in SvelteKit and modern web technologies.',
avatar: 'SC',
skills: ['SvelteKit', 'TypeScript', 'Node.js']
},
{
id: 2,
name: 'Marcus Johnson',
role: 'UI/UX Designer',
bio: 'Creative designer passionate about user experience and interface design.',
avatar: 'MJ',
skills: ['Design Systems', 'Prototyping', 'User Research']
},
{
id: 3,
name: 'Elena Rodriguez',
role: 'Backend Engineer',
bio: 'Backend specialist focusing on APIs, databases, and cloud architecture.',
avatar: 'ER',
skills: ['API Design', 'PostgreSQL', 'AWS']
}
];
}
async function animateStats() {
const targets = { projects: 127, clients: 89, years: 7, coffee: 2847 };
for (const [key, target] of Object.entries(targets)) {
let current = 0;
const increment = target / 50;
const timer = setInterval(() => {
current += increment;
if (current >= target) {
current = target;
clearInterval(timer);
}
stats[key] = Math.floor(current);
}, 30);
}
}
</script>
<svelte:head>
<title>About Us - SvelteKit App</title>
<meta name="description" content="Learn more about our team and what we do with SvelteKit" />
</svelte:head>
<section class="hero">
<div class="hero-content">
<h1>About Our Company</h1>
<p>We build amazing web experiences with SvelteKit, combining performance, developer experience, and modern best practices.</p>
</div>
</section>
<section class="stats">
<div class="stats-grid">
<div class="stat-card">
<div class="stat-number">{stats.projects}</div>
<div class="stat-label">Projects Completed</div>
</div>
<div class="stat-card">
<div class="stat-number">{stats.clients}</div>
<div class="stat-label">Happy Clients</div>
</div>
<div class="stat-card">
<div class="stat-number">{stats.years}</div>
<div class="stat-label">Years Experience</div>
</div>
<div class="stat-card">
<div class="stat-number">{stats.coffee}</div>
<div class="stat-label">Cups of Coffee</div>
</div>
</div>
</section>
<section class="mission">
<div class="mission-content">
<h2>Our Mission</h2>
<p>To create fast, accessible, and beautiful web applications that provide exceptional user experiences while maintaining developer productivity and joy.</p>
<div class="values">
<div class="value">
<h3>🚀 Performance</h3>
<p>We prioritize speed and efficiency in everything we build.</p>
</div>
<div class="value">
<h3>♿ Accessibility</h3>
<p>We believe the web should be usable by everyone, everywhere.</p>
</div>
<div class="value">
<h3>🎨 Design Excellence</h3>
<p>Beautiful interfaces that are both functional and delightful.</p>
</div>
<div class="value">
<h3>🔧 Developer Experience</h3>
<p>Tools and processes that make development a joy, not a chore.</p>
</div>
</div>
</div>
</section>
<section class="team">
<div class="team-content">
<h2>Meet Our Team</h2>
{#if loading}
<div class="loading">Loading team members...</div>
{:else if team.length > 0}
<div class="team-grid">
{#each team as member (member.id)}
<div class="team-member">
<div class="member-avatar">
{member.avatar}
</div>
<h3>{member.name}</h3>
<p class="member-role">{member.role}</p>
<p class="member-bio">{member.bio}</p>
<div class="member-skills">
{#each member.skills as skill}
<span class="skill-tag">{skill}</span>
{/each}
</div>
</div>
{/each}
</div>
{:else}
<p>No team members found.</p>
{/if}
</div>
</section>
<style>
.hero {
text-align: center;
padding: 4rem 2rem;
background: linear-gradient(135deg, #ff3e00 0%, #ff9e00 100%);
color: white;
margin: -2rem -2rem 0 -2rem;
}
.hero-content h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
font-weight: 700;
}
.hero-content p {
font-size: 1.2rem;
max-width: 600px;
margin: 0 auto;
opacity: 0.9;
}
.stats {
padding: 4rem 2rem;
background: #f8f9fa;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 2rem;
max-width: 800px;
margin: 0 auto;
}
.stat-card {
text-align: center;
padding: 2rem;
background: white;
border-radius: 1rem;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
}
.stat-card:hover {
transform: translateY(-5px);
}
.stat-number {
font-size: 2.5rem;
font-weight: bold;
color: #ff3e00;
margin-bottom: 0.5rem;
}
.stat-label {
color: #666;
font-weight: 500;
}
.mission {
padding: 4rem 2rem;
}
.mission-content {
max-width: 800px;
margin: 0 auto;
text-align: center;
}
.mission h2 {
font-size: 2rem;
color: #333;
margin-bottom: 1rem;
}
.mission > p {
font-size: 1.1rem;
color: #666;
line-height: 1.6;
margin-bottom: 3rem;
}
.values {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.value {
text-align: center;
padding: 1.5rem;
}
.value h3 {
font-size: 1.5rem;
margin-bottom: 1rem;
color: #333;
}
.value p {
color: #666;
line-height: 1.6;
}
.team {
padding: 4rem 2rem;
background: #f8f9fa;
}
.team-content {
max-width: 1000px;
margin: 0 auto;
}
.team h2 {
font-size: 2rem;
color: #333;
text-align: center;
margin-bottom: 3rem;
}
.loading {
text-align: center;
padding: 2rem;
font-size: 1.1rem;
color: #666;
}
.team-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 2rem;
}
.team-member {
background: white;
padding: 2rem;
border-radius: 1rem;
text-align: center;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
}
.team-member:hover {
transform: translateY(-5px);
}
.member-avatar {
width: 80px;
height: 80px;
background: linear-gradient(135deg, #ff3e00, #ff9e00);
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.5rem;
font-weight: bold;
margin: 0 auto 1.5rem;
}
.member-role {
color: #ff3e00;
font-weight: 500;
margin-bottom: 1rem;
}
.member-bio {
color: #666;
line-height: 1.6;
margin-bottom: 1.5rem;
}
.member-skills {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
justify-content: center;
}
.skill-tag {
background: #e3f2fd;
color: #1976d2;
padding: 0.25rem 0.75rem;
border-radius: 1rem;
font-size: 0.8rem;
font-weight: 500;
}
@media (max-width: 768px) {
.hero {
padding: 2rem 1rem;
margin: -1rem -1rem 0 -1rem;
}
.hero-content h1 {
font-size: 2rem;
}
.stats-grid,
.values,
.team-grid {
grid-template-columns: 1fr;
}
.stats,
.mission,
.team {
padding: 2rem 1rem;
}
}
</style>
💻 Rutas API y Manejo de Datos SvelteKit javascript
🟡 intermediate
⭐⭐⭐⭐
Construcción de APIs RESTful con rutas de servidor SvelteKit, manejo de formularios y gestión de datos
⏱️ 30 min
🏷️ sveltekit, api, backend, database, authentication
Prerequisites:
SvelteKit basics, REST APIs, JavaScript async/await, Database concepts
// SvelteKit API Routes and Data Handling
// 1. Simple API Route (src/routes/api/users/+server.js)
import { json } from '@sveltejs/kit';
// Mock user data (in real app, this would come from a database)
let users = [
{ id: 1, name: 'Sarah Chen', email: '[email protected]', age: 28, role: 'developer' },
{ id: 2, name: 'Marcus Johnson', email: '[email protected]', age: 32, role: 'designer' },
{ id: 3, name: 'Elena Rodriguez', email: '[email protected]', age: 30, role: 'manager' }
];
let nextId = 4;
// GET method - Fetch users
export async function GET({ url }) {
const searchParams = url.searchParams;
const id = searchParams.get('id');
const role = searchParams.get('role');
try {
if (id) {
// Get single user
const user = users.find(u => u.id === parseInt(id));
if (!user) {
return json({ error: 'User not found' }, { status: 404 });
}
return json({ user });
} else {
// Get all users with optional filtering
let filteredUsers = users;
if (role) {
filteredUsers = users.filter(u => u.role === role);
}
return json({
users: filteredUsers,
total: filteredUsers.length,
timestamp: new Date().toISOString()
});
}
} catch (error) {
console.error('GET users error:', error);
return json({ error: 'Internal server error' }, { status: 500 });
}
}
// POST method - Create new user
export async function POST({ request }) {
try {
const data = await request.json();
const { name, email, age, role = 'developer' } = data;
// Validation
if (!name || !email || !age) {
return json({
error: 'Missing required fields: name, email, age'
}, { status: 400 });
}
if (typeof age !== 'number' || age < 18 || age > 100) {
return json({
error: 'Age must be a number between 18 and 100'
}, { status: 400 });
}
// Check if email already exists
if (users.some(u => u.email === email)) {
return json({
error: 'Email already exists'
}, { status: 409 });
}
// Create new user
const newUser = {
id: nextId++,
name,
email,
age,
role,
createdAt: new Date().toISOString()
};
users.push(newUser);
return json({
message: 'User created successfully',
user: newUser
}, { status: 201 });
} catch (error) {
console.error('POST users error:', error);
return json({ error: 'Invalid JSON data' }, { status: 400 });
}
}
// PUT method - Update user
export async function PUT({ request, url }) {
try {
const id = url.searchParams.get('id');
const updateData = await request.json();
if (!id) {
return json({ error: 'User ID is required' }, { status: 400 });
}
const userIndex = users.findIndex(u => u.id === parseInt(id));
if (userIndex === -1) {
return json({ error: 'User not found' }, { status: 404 });
}
// Update user (merge with existing data)
const updatedUser = {
...users[userIndex],
...updateData,
id: users[userIndex].id,
updatedAt: new Date().toISOString()
};
users[userIndex] = updatedUser;
return json({
message: 'User updated successfully',
user: updatedUser
});
} catch (error) {
console.error('PUT users error:', error);
return json({ error: 'Invalid request data' }, { status: 400 });
}
}
// DELETE method - Delete user
export async function DELETE({ url }) {
try {
const id = url.searchParams.get('id');
if (!id) {
return json({ error: 'User ID is required' }, { status: 400 });
}
const userIndex = users.findIndex(u => u.id === parseInt(id));
if (userIndex === -1) {
return json({ error: 'User not found' }, { status: 404 });
}
const deletedUser = users.splice(userIndex, 1)[0];
return json({
message: 'User deleted successfully',
user: deletedUser
});
} catch (error) {
console.error('DELETE users error:', error);
return json({ error: 'Internal server error' }, { status: 500 });
}
}
// 2. Advanced API Route with Database Integration (src/routes/api/products/+server.js)
import { json } from '@sveltejs/kit';
import { Client } from 'pg';
// Database connection
const client = new Client({
connectionString: process.env.DATABASE_URL || 'postgresql://user:password@localhost:5432/myapp'
});
// Initialize database connection
async function initDB() {
try {
await client.connect();
console.log('Connected to database');
} catch (error) {
console.error('Database connection failed:', error);
throw error;
}
}
// GET products with pagination and filtering
export async function GET({ url }) {
try {
await initDB();
const searchParams = url.searchParams;
const page = parseInt(searchParams.get('page')) || 1;
const limit = parseInt(searchParams.get('limit')) || 10;
const category = searchParams.get('category');
const minPrice = searchParams.get('minPrice');
const maxPrice = searchParams.get('maxPrice');
const search = searchParams.get('search');
const offset = (page - 1) * limit;
// Build query
let query = 'SELECT * FROM products WHERE 1=1';
const queryParams = [];
let paramCount = 0;
if (category) {
query += ` AND category = $${++paramCount}`;
queryParams.push(category);
}
if (minPrice) {
query += ` AND price >= $${++paramCount}`;
queryParams.push(parseFloat(minPrice));
}
if (maxPrice) {
query += ` AND price <= $${++paramCount}`;
queryParams.push(parseFloat(maxPrice));
}
if (search) {
query += ` AND (name ILIKE $${++paramCount} OR description ILIKE $${++paramCount})`;
queryParams.push(`%${search}%`, `%${search}%`);
}
// Count total products
const countQuery = query.replace('SELECT * FROM', 'SELECT COUNT(*) FROM');
const countResult = await client.query(countQuery, queryParams);
const total = parseInt(countResult.rows[0].count);
// Get paginated products
query += ` ORDER BY created_at DESC LIMIT $${++paramCount} OFFSET $${++paramCount}`;
queryParams.push(limit, offset);
const result = await client.query(query, queryParams);
return json({
products: result.rows,
pagination: {
currentPage: page,
totalPages: Math.ceil(total / limit),
totalItems: total,
itemsPerPage: limit,
hasNext: page < Math.ceil(total / limit),
hasPrev: page > 1
},
filters: {
category,
minPrice,
maxPrice,
search
}
});
} catch (error) {
console.error('GET products error:', error);
return json({ error: 'Failed to fetch products' }, { status: 500 });
} finally {
await client.end();
}
}
// POST new product
export async function POST({ request }) {
try {
await initDB();
const productData = await request.json();
const { name, description, price, category, inStock = true, images = [] } = productData;
// Validation
if (!name || !price || !category) {
return json({
error: 'Missing required fields: name, price, category'
}, { status: 400 });
}
if (typeof price !== 'number' || price <= 0) {
return json({
error: 'Price must be a positive number'
}, { status: 400 });
}
// Insert product
const query = `
INSERT INTO products (name, description, price, category, in_stock, images, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, NOW(), NOW())
RETURNING *
`;
const values = [name, description, price, category, inStock, JSON.stringify(images)];
const result = await client.query(query, values);
return json({
message: 'Product created successfully',
product: result.rows[0]
}, { status: 201 });
} catch (error) {
console.error('POST products error:', error);
return json({ error: 'Failed to create product' }, { status: 500 });
} finally {
await client.end();
}
}
// 3. File Upload API (src/routes/api/upload/+server.js)
import { json } from '@sveltejs/kit';
import { writeFile, mkdir } from 'fs/promises';
import { join } from 'path';
import { v4 as uuidv4 } from 'uuid';
const UPLOAD_DIR = 'static/uploads';
// Ensure upload directory exists
async function ensureUploadDir() {
try {
await mkdir(UPLOAD_DIR, { recursive: true });
} catch (error) {
if (error.code !== 'EEXIST') {
throw error;
}
}
}
export async function POST({ request }) {
try {
await ensureUploadDir();
const formData = await request.formData();
const file = formData.get('file');
const description = formData.get('description') || '';
if (!file) {
return json({ error: 'No file provided' }, { status: 400 });
}
// Validate file type
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!allowedTypes.includes(file.type)) {
return json({
error: 'Invalid file type. Only JPEG, PNG, GIF, and WebP are allowed.'
}, { status: 400 });
}
// Validate file size (5MB limit)
const maxSize = 5 * 1024 * 1024; // 5MB
if (file.size > maxSize) {
return json({
error: 'File too large. Maximum size is 5MB.'
}, { status: 400 });
}
// Generate unique filename
const fileExtension = file.name.split('.').pop();
const fileName = `${uuidv4()}.${fileExtension}`;
const filePath = join(UPLOAD_DIR, fileName);
// Save file
const buffer = Buffer.from(await file.arrayBuffer());
await writeFile(filePath, buffer);
// Return file info
return json({
message: 'File uploaded successfully',
file: {
name: file.name,
size: file.size,
type: file.type,
url: `/uploads/${fileName}`,
description,
uploadedAt: new Date().toISOString()
}
}, { status: 201 });
} catch (error) {
console.error('File upload error:', error);
return json({ error: 'Failed to upload file' }, { status: 500 });
}
}
// 4. Contact Form API (src/routes/api/contact/+server.js)
import { json } from '@sveltejs/kit';
import { Resend } from 'resend';
const resend = new Resend(process.env.RESEND_API_KEY);
export async function POST({ request }) {
try {
const { name, email, subject, message } = await request.json();
// Validation
if (!name || !email || !subject || !message) {
return json({
error: 'All fields are required: name, email, subject, message'
}, { status: 400 });
}
// Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return json({
error: 'Invalid email address'
}, { status: 400 });
}
// Send email
try {
const { data, error } = await resend.emails.send({
from: '[email protected]',
to: '[email protected]',
subject: `Contact Form: ${subject}`,
html: `
<h2>New Contact Form Submission</h2>
<p><strong>Name:</strong> ${name}</p>
<p><strong>Email:</strong> ${email}</p>
<p><strong>Subject:</strong> ${subject}</p>
<p><strong>Message:</strong></p>
<p>${message.replace(/\n/g, '<br>')}</p>
`
});
if (error) {
console.error('Email send error:', error);
return json({
error: 'Failed to send message'
}, { status: 500 });
}
// Send confirmation email to user
await resend.emails.send({
from: '[email protected]',
to: email,
subject: 'Thank you for contacting us',
html: `
<h2>Thank you for contacting us!</h2>
<p>Dear ${name},</p>
<p>We have received your message and will get back to you as soon as possible.</p>
<p><strong>Your message:</strong></p>
<p>${message.replace(/\n/g, '<br>')}</p>
<p>Best regards,<br>The Team</p>
`
});
return json({
message: 'Message sent successfully',
id: data.id
});
} catch (emailError) {
console.error('Email service error:', emailError);
return json({
error: 'Failed to send message. Please try again later.'
}, { status: 500 });
}
} catch (error) {
console.error('Contact form error:', error);
return json({
error: 'Invalid request data'
}, { status: 400 });
}
}
// 5. Authentication API (src/routes/api/auth/login/+server.js)
import { json } from '@sveltejs/kit';
import jwt from 'jsonwebtoken';
import bcrypt from 'bcrypt';
// Mock user database (in production, use a real database)
const users = [
{
id: 1,
email: '[email protected]',
password: '$2b$10$rJ8H8QzvGYHZJGZ8YwK2X.3Qh8h8h8h8h8h8h8h8h8h8h8h8h8h8h8h8h8', // 'password'
name: 'Admin User',
role: 'admin'
},
{
id: 2,
email: '[email protected]',
password: '$2b$10$rJ8H8QzvGYHZJGZ8YwK2X.3Qh8h8h8h8h8h8h8h8h8h8h8h8h8h8h8h8h8', // 'password'
name: 'Regular User',
role: 'user'
}
];
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
export async function POST({ request, cookies }) {
try {
const { email, password } = await request.json();
// Validation
if (!email || !password) {
return json({
error: 'Email and password are required'
}, { status: 400 });
}
// Find user
const user = users.find(u => u.email === email);
if (!user) {
return json({
error: 'Invalid credentials'
}, { status: 401 });
}
// Verify password
const isValidPassword = await bcrypt.compare(password, user.password);
if (!isValidPassword) {
return json({
error: 'Invalid credentials'
}, { status: 401 });
}
// Generate JWT token
const token = jwt.sign(
{
userId: user.id,
email: user.email,
role: user.role
},
JWT_SECRET,
{ expiresIn: '24h' }
);
// Set HTTP-only cookie
cookies.set('auth-token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 60 * 60 * 24, // 24 hours
path: '/'
});
return json({
message: 'Login successful',
user: {
id: user.id,
email: user.email,
name: user.name,
role: user.role
},
token
});
} catch (error) {
console.error('Login error:', error);
return json({
error: 'Internal server error'
}, { status: 500 });
}
}
// 6. Data Loading with load function (src/routes/users/+page.server.js)
export async function load({ fetch }) {
try {
const response = await fetch('/api/users');
if (!response.ok) {
throw new Error('Failed to fetch users');
}
const data = await response.json();
return {
users: data.users || [],
total: data.total || 0,
timestamp: data.timestamp || new Date().toISOString()
};
} catch (error) {
console.error('Load users error:', error);
return {
users: [],
total: 0,
error: error.message,
timestamp: new Date().toISOString()
};
}
}
// 7. Form Actions (src/routes/users/+page.server.js)
import { fail, redirect } from '@sveltejs/kit';
export const actions = {
create: async ({ request }) => {
const data = await request.formData();
const name = data.get('name');
const email = data.get('email');
const age = data.get('age');
const role = data.get('role');
// Validation
if (!name || !email || !age) {
return fail(400, {
error: 'Missing required fields',
values: { name, email, age, role }
});
}
try {
const response = await fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: name.toString(),
email: email.toString(),
age: parseInt(age.toString()),
role: role?.toString() || 'developer'
})
});
if (!response.ok) {
const errorData = await response.json();
return fail(response.status, {
error: errorData.error || 'Failed to create user',
values: { name, email, age, role }
});
}
return { success: true };
} catch (error) {
return fail(500, {
error: 'Network error. Please try again.',
values: { name, email, age, role }
});
}
},
delete: async ({ request }) => {
const data = await request.formData();
const id = data.get('id');
if (!id) {
return fail(400, { error: 'User ID is required' });
}
try {
const response = await fetch(`/api/users?id=${id}`, {
method: 'DELETE'
});
if (!response.ok) {
const errorData = await response.json();
return fail(response.status, {
error: errorData.error || 'Failed to delete user'
});
}
return { success: true };
} catch (error) {
return fail(500, {
error: 'Network error. Please try again.'
});
}
}
};
💻 Características Avanzadas SvelteKit javascript
🔴 complex
⭐⭐⭐⭐⭐
Conceptos avanzados de SvelteKit incluyendo SSR, SSG, acciones de formulario, autenticación, middleware y despliegue
⏱️ 45 min
🏷️ sveltekit, ssr, ssg, auth, production, deployment
Prerequisites:
Advanced SvelteKit, Database concepts, Authentication patterns, DevOps basics
// SvelteKit Advanced Features
// 1. Advanced Load Functions with Error Handling (src/routes/blog/[slug]/+page.server.js)
import { error } from '@sveltejs/kit';
export async function load({ params, fetch, parent }) {
const { slug } = params;
try {
// Get parent data (from layout load function)
const parentData = await parent();
// Fetch blog post
const postResponse = await fetch(`/api/blog/${slug}`);
if (!postResponse.ok) {
if (postResponse.status === 404) {
throw error(404, 'Blog post not found');
}
throw error(500, 'Failed to fetch blog post');
}
const postData = await postResponse.json();
const post = postData.post;
// Fetch related posts
const relatedResponse = await fetch(`/api/blog/related?category=${post.category}&exclude=${slug}&limit=3`);
const relatedData = relatedResponse.ok ? await relatedResponse.json() : { posts: [] };
// Fetch comments
const commentsResponse = await fetch(`/api/blog/${slug}/comments`);
const commentsData = commentsResponse.ok ? await commentsResponse.json() : { comments: [] };
return {
post,
relatedPosts: relatedData.posts || [],
comments: commentsData.comments || [],
parentData
};
} catch (err) {
// If it's already a SvelteKit error, re-throw it
if (err.status) {
throw err;
}
console.error('Load function error:', err);
throw error(500, 'Internal server error');
}
}
// 2. Universal Load Function (works on server and client) (src/routes/posts/[id]/+page.js)
import { goto } from '$app/navigation';
export async function load({ params, fetch }) {
const { id } = params;
// This function runs on both server and client
const fetchPost = async () => {
try {
const response = await fetch(`/api/posts/${id}`);
if (!response.ok) {
if (response.status === 404) {
// On client, redirect to 404 page
if (typeof window !== 'undefined') {
await goto('/404', { replaceState: true });
}
return { post: null, error: 'Post not found' };
}
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
return { post: data.post, error: null };
} catch (err) {
console.error('Failed to fetch post:', err);
return { post: null, error: 'Failed to fetch post' };
}
};
const result = await fetchPost();
return {
post: result.post,
error: result.error,
streamed: {
// Stream data that's not critical for initial render
comments: fetch(`/api/posts/${id}/comments`).then(res =>
res.ok ? res.json() : { comments: [] }
)
}
};
}
// 3. Advanced Form Actions with Validation (src/routes/contact/+page.server.js)
import { fail, redirect } from '@sveltejs/kit';
import { z } from 'zod';
// Validation schema using Zod
const contactSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
subject: z.string().min(5, 'Subject must be at least 5 characters'),
message: z.string().min(10, 'Message must be at least 10 characters'),
newsletter: z.boolean().default(false),
priority: z.enum(['low', 'medium', 'high']).default('medium')
});
export const actions = {
default: async ({ request, getClientAddress }) => {
const formData = await request.formData();
const data = Object.fromEntries(formData);
// Validate data
try {
const validatedData = contactSchema.parse(data);
// Get client IP for rate limiting
const clientIP = getClientAddress();
// Check rate limiting (example: max 5 submissions per hour per IP)
const rateLimitKey = `contact_rate_limit_${clientIP}`;
// In production, you'd use Redis or similar for rate limiting
// Send email
await sendContactEmail(validatedData);
// Send confirmation if requested
if (validatedData.newsletter) {
await addToNewsletter(validatedData.email);
}
// Log the contact submission
await logContactSubmission({
...validatedData,
ip: clientIP,
timestamp: new Date(),
userAgent: request.headers.get('user-agent')
});
return {
success: true,
message: 'Thank you for your message! We will get back to you soon.'
};
} catch (validationError) {
if (validationError instanceof z.ZodError) {
return fail(400, {
error: 'Please fix the validation errors',
errors: validationError.errors.reduce((acc, err) => {
acc[err.path[0]] = err.message;
return acc;
}, {}),
values: data
});
}
console.error('Contact form error:', validationError);
return fail(500, {
error: 'Something went wrong. Please try again.',
values: data
});
}
}
};
async function sendContactEmail(data) {
// Email sending logic here
console.log('Sending email:', data);
await new Promise(resolve => setTimeout(resolve, 100)); // Simulate email sending
}
async function addToNewsletter(email) {
// Newsletter subscription logic
console.log('Adding to newsletter:', email);
}
async function logContactSubmission(data) {
// Logging logic
console.log('Logging contact submission:', data);
}
// 4. Authentication Middleware (src/hooks.server.js)
import { redirect } from '@sveltejs/kit';
import jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';
const protectedRoutes = ['/dashboard', '/profile', '/admin'];
const adminRoutes = ['/admin'];
export async function handle({ event, resolve }) {
const { url, request, cookies } = event;
// Skip authentication for API routes and static files
if (url.pathname.startsWith('/api/') ||
url.pathname.startsWith('/_app/') ||
url.pathname.startsWith('/static/')) {
return resolve(event);
}
// Get auth token from cookie or Authorization header
const token = cookies.get('auth-token') ||
request.headers.get('authorization')?.replace('Bearer ', '');
// Check if route requires authentication
const isProtectedRoute = protectedRoutes.some(route =>
url.pathname.startsWith(route)
);
const isAdminRoute = adminRoutes.some(route =>
url.pathname.startsWith(route)
);
if (isProtectedRoute || isAdminRoute) {
if (!token) {
// Redirect to login with return URL
const loginUrl = new URL('/login', url.origin);
loginUrl.searchParams.set('returnUrl', url.pathname + url.search);
throw redirect(302, loginUrl.toString());
}
try {
// Verify JWT token
const decoded = jwt.verify(token, JWT_SECRET);
// Add user data to event.locals
event.locals.user = {
id: decoded.userId,
email: decoded.email,
role: decoded.role
};
// Check admin access
if (isAdminRoute && decoded.role !== 'admin') {
throw redirect(302, '/unauthorized');
}
} catch (tokenError) {
console.error('Token verification error:', tokenError);
// Clear invalid token
cookies.delete('auth-token', { path: '/' });
// Redirect to login
const loginUrl = new URL('/login', url.origin);
loginUrl.searchParams.set('returnUrl', url.pathname + url.search);
throw redirect(302, loginUrl.toString());
}
}
// Continue with the request
const response = await resolve(event);
// Add security headers
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
return response;
}
// 5. Database Integration with Prisma (src/lib/server/database.js)
import { PrismaClient } from '@prisma/client';
// Singleton pattern for database connection
let prisma;
export function getPrismaClient() {
if (!prisma) {
prisma = new PrismaClient({
log: ['query', 'info', 'warn', 'error'],
errorFormat: 'pretty'
});
}
// Handle graceful shutdown in development
if (process.env.NODE_ENV === 'development') {
globalThis.prisma = prisma;
}
return prisma;
}
// Database helper functions
export const db = {
// User operations
users: {
async findById(id) {
return await getPrismaClient().user.findUnique({
where: { id: parseInt(id) },
include: {
posts: true,
comments: true
}
});
},
async findByEmail(email) {
return await getPrismaClient().user.findUnique({
where: { email }
});
},
async create(data) {
return await getPrismaClient().user.create({
data,
select: {
id: true,
email: true,
name: true,
role: true,
createdAt: true
}
});
},
async update(id, data) {
return await getPrismaClient().user.update({
where: { id: parseInt(id) },
data
});
},
async delete(id) {
return await getPrismaClient().user.delete({
where: { id: parseInt(id) }
});
}
},
// Post operations
posts: {
async findMany(options = {}) {
const { page = 1, limit = 10, category, authorId } = options;
const skip = (page - 1) * limit;
const where = {};
if (category) where.category = category;
if (authorId) where.authorId = parseInt(authorId);
const [posts, total] = await Promise.all([
getPrismaClient().post.findMany({
where,
include: {
author: {
select: {
id: true,
name: true,
email: true
}
},
_count: {
select: {
comments: true,
likes: true
}
}
},
orderBy: { createdAt: 'desc' },
skip,
take: limit
}),
getPrismaClient().post.count({ where })
]);
return {
posts,
pagination: {
page,
limit,
total,
totalPages: Math.ceil(total / limit),
hasNext: page < Math.ceil(total / limit),
hasPrev: page > 1
}
};
},
async findBySlug(slug) {
return await getPrismaClient().post.findUnique({
where: { slug },
include: {
author: true,
comments: {
include: {
author: true
},
orderBy: { createdAt: 'desc' }
},
tags: true
}
});
},
async create(data) {
return await getPrismaClient().post.create({
data,
include: {
author: {
select: {
id: true,
name: true,
email: true
}
}
}
});
}
}
};
// 6. Error Handling (src/hooks.client.js)
import { handleError } from '$lib/utils/errors';
// Global error handler for client-side errors
export function handleError({ error, event }) {
console.error('Client error:', error, event);
// Log to error reporting service
handleError(error, {
url: event.url.toString(),
route: event.route.id,
userAgent: navigator.userAgent,
timestamp: new Date().toISOString()
});
}
// 7. Custom App Configuration (src/app.html)
<!DOCTYPE html>
<html lang="en" %sveltekit.theme%>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
<meta name="viewport" content="width=device-width" />
<meta name="theme-color" content="#ff3e00" />
<!-- Preload critical resources -->
<link rel="preload" href="%sveltekit.assets%/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>
<!-- SEO Meta Tags -->
%sveltekit.head%
<!-- Structured Data -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebSite",
"name": "SvelteKit App",
"description": "A modern web application built with SvelteKit",
"url": "https://yourapp.com"
}
</script>
</head>
<body data-sveltekit-preload-data="hover" class="dark">
<div style="display: contents">%sveltekit.body%</div>
<!-- Service Worker Registration -->
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
</script>
</body>
</html>
// 8. Service Worker (src/service-worker.ts)
import { build, files, version } from '$service-worker';
const CACHE_NAME = `cache-${version}`;
// Files to cache
const FILES_TO_CACHE = [
...build,
...files
];
// Install event - cache files
self.addEventListener('install', (event) => {
console.log('Service Worker: Installing...');
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => {
console.log('Service Worker: Caching files');
return cache.addAll(FILES_TO_CACHE);
})
.then(() => self.skipWaiting())
);
});
// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
console.log('Service Worker: Activating...');
event.waitUntil(
caches.keys()
.then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
console.log('Service Worker: Deleting old cache:', cacheName);
return caches.delete(cacheName);
}
})
);
})
.then(() => self.clients.claim())
);
});
// Fetch event - serve cached content when offline
self.addEventListener('fetch', (event) => {
// Skip cross-origin requests
if (!event.request.url.startsWith(self.location.origin)) {
return;
}
event.respondWith(
caches.match(event.request)
.then((response) => {
// Return cached version or fetch from network
return response || fetch(event.request);
})
.catch(() => {
// Return offline page for navigation requests
if (event.request.mode === 'navigate') {
return caches.match('/offline');
}
})
);
});
// Background sync for offline actions
self.addEventListener('sync', (event) => {
if (event.tag === 'background-sync') {
event.waitUntil(doBackgroundSync());
}
});
async function doBackgroundSync() {
// Handle background synchronization
console.log('Service Worker: Background sync');
}
// Push notifications
self.addEventListener('push', (event) => {
const options = {
body: event.data.text(),
icon: '/icon-192.png',
badge: '/badge.png',
vibrate: [100, 50, 100],
data: {
dateOfArrival: Date.now(),
primaryKey: 1
},
actions: [
{
action: 'explore',
title: 'Explore',
icon: '/images/checkmark.png'
},
{
action: 'close',
title: 'Close',
icon: '/images/xmark.png'
}
]
};
event.waitUntil(
self.registration.showNotification('SvelteKit App', options)
);
});
// 9. Deployment Configuration (svelte.config.js)
import adapter from '@sveltejs/adapter-node';
import { vitePreprocess } from '@sveltejs/kit/vite';
/** @type {import('@sveltejs/kit').Config} */
const config = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: vitePreprocess(),
kit: {
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter({
// Node.js adapter options
out: 'build',
precompress: false,
env: {
host: 'HOST',
port: 'PORT'
}
}),
// Alias for cleaner imports
alias: {
'$components': 'src/lib/components',
'$utils': 'src/lib/utils',
'$stores': 'src/lib/stores',
'$types': 'src/lib/types'
},
// CSRF protection
csrf: {
checkOrigin: process.env.NODE_ENV === 'production'
},
// Headers for security and performance
headers: {
'security': [
{
source: '/(.*)',
headers: [
{
name: 'X-Frame-Options',
value: 'DENY'
},
{
name: 'X-Content-Type-Options',
value: 'nosniff'
},
{
name: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin'
},
{
name: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()'
}
]
}
]
}
}
};
export default config;
// 10. Environment Configuration (.env.example)
# Database
DATABASE_URL="postgresql://username:password@localhost:5432/myapp"
# Authentication
JWT_SECRET="your-super-secret-jwt-key"
SESSION_SECRET="your-session-secret"
# Email Service (Resend)
RESEND_API_KEY="your-resend-api-key"
RESEND_FROM_EMAIL="[email protected]"
# OAuth Providers
GOOGLE_CLIENT_ID="your-google-client-id"
GOOGLE_CLIENT_SECRET="your-google-client-secret"
GITHUB_CLIENT_ID="your-github-client-id"
GITHUB_CLIENT_SECRET="your-github-client-secret"
# External APIs
STRIPE_SECRET_KEY="sk_test_..."
STRIPE_WEBHOOK_SECRET="whsec_..."
# Analytics
GOOGLE_ANALYTICS_ID="G-XXXXXXXXXX"
SENTRY_DSN="https://your-sentry-dsn"
# File Upload
UPLOAD_MAX_SIZE="5242880" # 5MB in bytes
UPLOAD_ALLOWED_TYPES="image/jpeg,image/png,image/gif,image/webp"
# Rate Limiting
RATE_LIMIT_WINDOW="3600000" # 1 hour in milliseconds
RATE_LIMIT_MAX_REQUESTS="100"
# Cache
REDIS_URL="redis://localhost:6379"
CACHE_TTL="3600" # 1 hour in seconds
# Application
NODE_ENV="development"
PORT="5173"
HOST="localhost"
# Feature Flags
ENABLE_REGISTRATION="true"
ENABLE_NEWSLETTER="true"
ENABLE_COMMENTS="true"
MAINTENANCE_MODE="false"
# Security
CORS_ORIGIN="http://localhost:5173"
COOKIE_SECURE="false" # Set to true in production
COOKIE_SAME_SITE="lax"