logo hsb.horse
← Zur Blog-Übersicht

Blog

Implementierung eines nur für Entwicklung vorgesehenen Live-Editors in Astro-Projekten

So implementieren Sie einen browserbasierten Editor, der nur im Entwicklungsmodus in Astro funktioniert. Verwenden Sie Vite-Middleware und React, um das Content-Management während der Entwicklung komfortabel zu gestalten, ohne die Produktions-Builds zu beeinflussen.

Veröffentlicht:

Beim Erstellen statischer Websites mit Astro ist Git-basiertes Content-Management der Standard. Die direkte Bearbeitung im Browser während der Entwicklung beschleunigt jedoch den Workflow.

Sie können Inhalte bearbeiten, ohne zwischen Editor und Browser zu wechseln. Änderungen werden sofort auf dem Bildschirm angezeigt. Bilder können per GUI hochgeladen werden.

Für das PromptLex-Projekt habe ich einen Editor erstellt, der nur im Entwicklungsmodus funktioniert. Es hat keine Auswirkungen auf Produktions-Builds.

Warum einen Live-Editor erstellen

Die Verwendung von Git-basiertem Management für die Produktion ist offensichtlich, aber die Entwicklung ist anders.

Tests und Inhaltserstellung wurden viel einfacher. Die Iterationsgeschwindigkeit beim Prototyping nahm zu, und die Bildverwaltung ist in derselben UI abgeschlossen. Sie können problemlos Dummy-Daten für Entwicklungstests erstellen und löschen.

Architektur

Es besteht aus drei Komponenten.

React-Komponente für die UI. Vite-Middleware für CRUD-Operations-API-Endpunkte. Astro-Seite als Einstiegspunkt, der den Editor nur im DEV-Modus lädt.

Schritt 1: Verwaltungsseite erstellen

Erstellen Sie eine Astro-Seite, die nur im Entwicklungsmodus funktioniert.

src/pages/admin/index.astro
---
import Base from '../../layouts/Base.astro';
import AdminEditor from '../../components/AdminEditor';
const isDev = import.meta.env.DEV;
---
<Base title="Verwaltung">
<meta slot="head" name="robots" content="noindex, nofollow" />
{isDev ? (
<AdminEditor client:only="react" />
) : (
<div>
<h1>Verwaltung</h1>
<p>Die Verwaltung ist nur im Entwicklungsmodus verfügbar.</p>
<p>Führen Sie <code>npm run dev</code> aus, um darauf zuzugreifen.</p>
</div>
)}
</Base>

Durch Angabe von client:only="react" wird die Komponente nur auf der Client-Seite geladen.

Schritt 2: API-Middleware erstellen

Erstellen Sie eine Astro-Integration, die API-Endpunkte zum Entwicklungsserver hinzufügt.

src/integrations/admin-api.ts
import type { AstroIntegration } from 'astro';
import { readFileSync, writeFileSync, readdirSync } from 'fs';
import { join } from 'path';
export default function adminApi(): AstroIntegration {
return {
name: 'admin-api',
hooks: {
'astro:server:setup': ({ server }) => {
server.middlewares.use(async (req, res, next) => {
const url = req.url || '';
const method = req.method || 'GET';
if (!url.startsWith('/api/')) {
return next();
}
const contentDir = join(process.cwd(), 'src', 'content');
// GET /api/items - Alle Elemente auflisten
if (url === '/api/items' && method === 'GET') {
const files = readdirSync(contentDir).filter(f => f.endsWith('.json'));
const items = files.map(file => {
const content = readFileSync(join(contentDir, file), 'utf-8');
return JSON.parse(content);
});
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(items));
return;
}
// POST /api/items - Neues Element erstellen
if (url === '/api/items' && method === 'POST') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
const data = JSON.parse(body);
const id = generateId(data.text);
const filePath = join(contentDir, `${id}.json`);
const item = {
id,
text: data.text,
createdAt: new Date().toISOString(),
};
writeFileSync(filePath, JSON.stringify(item, null, 2));
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(item));
});
return;
}
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Not found' }));
});
},
},
};
}
function generateId(text: string): string {
return text.slice(0, 12).replace(/\W/g, '').toLowerCase();
}

Schritt 3: React-Komponente erstellen

Schreiben Sie eine React-Komponente, die die API aufruft.

src/components/AdminEditor.tsx
import React, { useState, useEffect } from 'react';
interface Item {
id: string;
text: string;
createdAt: string;
}
export default function AdminEditor() {
const [items, setItems] = useState<Item[]>([]);
const [newText, setNewText] = useState('');
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchItems();
}, []);
const fetchItems = async () => {
const response = await fetch('/api/items');
const data = await response.json();
setItems(data);
setLoading(false);
};
const handleCreate = async (e: React.FormEvent) => {
e.preventDefault();
if (!newText.trim()) return;
const response = await fetch('/api/items', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text: newText }),
});
const newItem = await response.json();
setItems([newItem, ...items]);
setNewText('');
};
if (loading) return <div>Lädt...</div>;
return (
<div style={{ maxWidth: '1200px', margin: '0 auto', padding: '20px' }}>
<h1>Content-Editor</h1>
<form onSubmit={handleCreate}>
<textarea
value={newText}
onChange={(e) => setNewText(e.target.value)}
placeholder="Inhalt eingeben..."
rows={4}
style={{ width: '100%', padding: '10px' }}
/>
<button type="submit">Erstellen</button>
</form>
<div>
<h2>Elemente ({items.length})</h2>
{items.map(item => (
<div key={item.id} style={{ border: '1px solid #ddd', padding: '10px', margin: '10px 0' }}>
<strong>{item.id}</strong>
<p>{item.text}</p>
<small>{new Date(item.createdAt).toLocaleString()}</small>
</div>
))}
</div>
</div>
);
}

Schritt 4: Integration registrieren

Fügen Sie die Admin-API-Integration zu astro.config.mjs hinzu.

import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import adminApi from './src/integrations/admin-api';
export default defineConfig({
integrations: [
react(),
adminApi(),
],
});

Erweiterte Funktionen

Datei-Upload-Unterstützung

Laden Sie Bilder mit mehrteiligen Formulardaten hoch.

async function parseMultipart(req) {
return new Promise((resolve) => {
const chunks = [];
req.on('data', chunk => chunks.push(chunk));
req.on('end', () => {
const buffer = Buffer.concat(chunks);
// Boundary analysieren und Dateien extrahieren
// ...
resolve({ fields, files });
});
});
}

Duplikatsprävention

Vermeiden Sie Duplikate mit Content-Hashing.

import { createHash } from 'crypto';
function generateId(text: string): string {
const hash = createHash('sha256');
hash.update(text);
return hash.digest('hex').substring(0, 12);
}

Löschfunktion

Zeigen Sie vor dem Löschen einen Bestätigungsdialog an.

const handleDelete = async (id: string) => {
if (!confirm('Dieses Element löschen?')) return;
await fetch(`/api/items/${id}`, { method: 'DELETE' });
setItems(items.filter(item => item.id !== id));
};

Best Practices

Überprüfen Sie immer, dass es nur für die Entwicklung ist, mit import.meta.env.DEV. Fügen Sie noindex-Meta-Tag zu Verwaltungsseiten hinzu. Machen Sie Typen mit TypeScript-Interfaces explizit. Schreiben Sie klare Fehlermeldungen. Validieren Sie Eingaben vor dem Speichern. Verwenden Sie path.join() für Dateioperationen.

Auswirkungen auf die Produktion

Keine Auswirkungen auf Produktions-Builds ist der Vorteil dieses Ansatzes.

Der Editor funktioniert nur während astro dev. Produktions-Builds enthalten keine zusätzlichen Routen. API-Endpunkte werden nicht offengelegt. Statische Sites bleiben statisch.

Das Erstellen eines Live-Editors für Astro-Projekte verändert die Entwicklungserfahrung. Mit Vite-Middleware und React können Sie eine Content-Management-Oberfläche erstellen, die nur während der Entwicklung existiert.

Die vollständige Implementierung befindet sich im PromptLex-Repository.

Referenzen