Skip to content

Add Svelte vào Astro

Astro hỗ trợ tích hợp nhiều UI framework khác nhau, trong đó có Svelte. Bài viết này hướng dẫn bạn cách thêm Svelte vào Astro project một cách chi tiết nhất.

Terminal window
npm install svelte @astrojs/svelte

Hoặc dùng các package manager khác:

Terminal window
# Yarn
yarn add svelte @astrojs/svelte
# pnpm
pnpm add svelte @astrojs/svelte
import { defineConfig } from 'astro/config';
import svelte from '@astrojs/svelte'; // Import Svelte integration
export default defineConfig({
integrations: [
svelte(), // Thêm vào integrations array
],
});

Tạo folder src/components/ (nếu chưa có) và file component:

src/components/Counter.svelte:

<script>
let count = 0;
function increment() {
count += 1;
}
function decrement() {
count -= 1;
}
</script>
<div class="counter">
<button on:click={decrement}>-</button>
<span>{count}</span>
<button on:click={increment}>+</button>
</div>
<style>
.counter {
display: flex;
gap: 1rem;
align-items: center;
}
button {
padding: 0.5rem 1rem;
font-size: 1.2rem;
cursor: pointer;
}
span {
font-size: 1.5rem;
font-weight: bold;
min-width: 3rem;
text-align: center;
}
</style>

Bước 4: Sử dụng component trong Astro

Section titled “Bước 4: Sử dụng component trong Astro”

Trong file .astro:

---
import Counter from '../components/Counter.svelte';
---
<h1>Counter Example</h1>
<!-- Dùng client:load để component hoạt động -->
<Counter client:load />

Trong file .mdx:

import Counter from '../components/Counter.svelte';
# My Article
Some text here...
<Counter client:load />
DirectiveKhi nào dùngMô tả
client:loadComponent cần JS ngayHydrate ngay khi page load
client:idleComponent không gấpHydrate khi browser idle
client:visibleComponent dưới foldHydrate khi scroll vào viewport
client:mediaResponsiveHydrate ở certain media query
client:onlyNo SSRChỉ chạy ở client, không server-render
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"strict": true,
"types": ["astro/client", "svelte"]
}
}
<script lang="ts">
interface Props {
title: string;
count?: number;
}
export let title: string;
export let count: number = 0;
function increment(): void {
count += 1;
}
</script>
<h2>{title}</h2>
<p>Count: {count}</p>
<button on:click={increment}>+</button>
<style>
/* CSS này chỉ apply cho component này */
.my-class {
color: blue;
}
</style>
<style>
:global(body) {
/* Apply cho toàn bộ page */
background: #f0f0f0;
}
</style>
<style>
.card {
background: var(--theme-bg, white);
color: var(--theme-text, black);
}
</style>
Error: Cannot find module './Counter.svelte'

Fix: Thêm extension vào import

import Counter from './Counter.svelte';
Hydration failed because the initial UI does not match

Fix: Dùng client:only hoặc kiểm tra typeof window

<script>
import { onMount } from 'svelte';
let data;
onMount(() => {
// Code dùng window ở đây
data = window.localStorage.getItem('key');
});
</script>

Fix: Đảm bảo không bị override bởi global styles

<style>
/* Tăng specificity */
div.my-component {
color: red;
}
/* Hoặc dùng :global nếu cần */
:global(.external-class) {
color: blue;
}
</style>
<script lang="ts">
interface Todo {
id: number;
text: string;
done: boolean;
}
let todos: Todo[] = [];
let newTodo = '';
function addTodo() {
if (newTodo.trim()) {
todos = [...todos, {
id: Date.now(),
text: newTodo,
done: false
}];
newTodo = '';
}
}
function toggleTodo(id: number) {
todos = todos.map(t =>
t.id === id ? { ...t, done: !t.done } : t
);
}
function removeTodo(id: number) {
todos = todos.filter(t => t.id !== id);
}
</script>
<div class="todo-list">
<div class="add-todo">
<input
bind:value={newTodo}
placeholder="Add new todo..."
on:keypress={(e) => e.key === 'Enter' && addTodo()}
/>
<button on:click={addTodo}>Add</button>
</div>
<ul>
{#each todos as todo (todo.id)}
<li class:done={todo.done}>
<input
type="checkbox"
checked={todo.done}
on:change={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button on:click={() => removeTodo(todo.id)}>×</button>
</li>
{/each}
</ul>
</div>
<style>
.todo-list {
max-width: 400px;
}
.add-todo {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
input[type="text"] {
flex: 1;
padding: 0.5rem;
}
ul {
list-style: none;
padding: 0;
}
li {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem;
border-bottom: 1px solid #eee;
}
li.done span {
text-decoration: line-through;
opacity: 0.6;
}
button {
background: #ff4444;
color: white;
border: none;
padding: 0.25rem 0.5rem;
cursor: pointer;
}
</style>