Astro Version โ
HaloLight Astro version is built on Astro 5, featuring Islands architecture with zero JS initial load and ultimate performance, supporting multi-framework component integration.
Live Preview: https://halolight-astro.h7ml.cn/
GitHub: https://github.com/halolight/halolight-astro
Features โ
- ๐๏ธ Islands Architecture - Zero JS by default, hydrate interactive components on demand
- โก Ultimate Performance - Zero JavaScript on initial load, Lighthouse 100 score
- ๐ Multi-framework Integration - Support React, Vue, Svelte, Solid components in one project
- ๐ Content-first - Native Markdown/MDX support, content collections
- ๐ View Transitions - Native View Transitions API support
- ๐ SSR/SSG/Hybrid - Flexible rendering modes
- ๐ฆ API Endpoints - Native REST API endpoint support
- ๐จ Theme System - Light/dark theme switching
- ๐ Authentication - Complete login/register/forgot password flow
- ๐ Dashboard - Data visualization and business management
Tech Stack โ
| Technology | Version | Description |
|---|---|---|
| Astro | 5.x | Islands architecture framework |
| TypeScript | 5.x | Type safety |
| Tailwind CSS | 3.x | Atomic CSS |
| Vite | Built-in | Build tool |
| @astrojs/node | 9.x | Node.js adapter |
| Vitest | 4.x | Unit testing |
Core Features โ
- Islands Architecture - Zero JS by default, hydrate interactive components on demand
- Multi-framework Support - Use React, Vue, Svelte components in the same project
- Content-first - Static-first, ultimate initial load performance
- SSR Support - Server-side rendering via @astrojs/node adapter
- File-based Routing - Automatic routing based on file system
- API Endpoints - Native support for REST API endpoints
Directory Structure โ
halolight-astro/
โโโ src/
โ โโโ pages/ # File-based routing
โ โ โโโ index.astro # Home
โ โ โโโ privacy.astro # Privacy Policy
โ โ โโโ terms.astro # Terms of Service
โ โ โโโ auth/ # Auth pages
โ โ โ โโโ login.astro
โ โ โ โโโ register.astro
โ โ โ โโโ forgot-password.astro
โ โ โ โโโ reset-password.astro
โ โ โโโ dashboard/ # Dashboard pages
โ โ โ โโโ index.astro # Dashboard home
โ โ โ โโโ analytics.astro # Analytics
โ โ โ โโโ users.astro # User management
โ โ โ โโโ accounts.astro # Account management
โ โ โ โโโ documents.astro # Document management
โ โ โ โโโ files.astro # File management
โ โ โ โโโ messages.astro # Message center
โ โ โ โโโ notifications.astro
โ โ โ โโโ calendar.astro # Calendar
โ โ โ โโโ profile.astro # Profile
โ โ โ โโโ settings/ # Settings
โ โ โโโ api/ # API endpoints
โ โ โโโ auth/
โ โ โโโ login.ts
โ โ โโโ register.ts
โ โ โโโ forgot-password.ts
โ โ โโโ reset-password.ts
โ โโโ layouts/ # Layout components
โ โ โโโ Layout.astro # Base layout
โ โ โโโ AuthLayout.astro # Auth layout
โ โ โโโ DashboardLayout.astro # Dashboard layout
โ โ โโโ LegalLayout.astro # Legal pages layout
โ โโโ components/ # UI components
โ โ โโโ dashboard/
โ โ โโโ Sidebar.astro # Sidebar
โ โ โโโ Header.astro # Top navigation
โ โโโ styles/ # Global styles
โ โ โโโ globals.css
โ โโโ assets/ # Static assets
โโโ public/ # Public assets
โโโ tests/ # Test files
โโโ astro.config.mjs # Astro config
โโโ tailwind.config.mjs # Tailwind config
โโโ vitest.config.ts # Test config
โโโ package.jsonQuick Start โ
Environment Requirements โ
- Node.js >= 18.0.0
- pnpm >= 9.x
Installation โ
git clone https://github.com/halolight/halolight-astro.git
cd halolight-astro
pnpm installEnvironment Variables โ
cp .env.example .env.local# .env.local example
PUBLIC_API_URL=/api
PUBLIC_MOCK=true
PUBLIC_DEMO_EMAIL=admin@halolight.h7ml.cn
PUBLIC_DEMO_PASSWORD=123456
PUBLIC_SHOW_DEMO_HINT=true
PUBLIC_APP_TITLE=Admin Pro
PUBLIC_BRAND_NAME=HalolightStart Development โ
pnpm devVisit http://localhost:4321
Build for Production โ
pnpm build
pnpm previewDemo Account โ
| Role | Password | |
|---|---|---|
| Admin | admin@halolight.h7ml.cn | 123456 |
| User | user@halolight.h7ml.cn | 123456 |
Core Functionality โ
Islands Architecture โ
Astro's Islands architecture allows pages to be static HTML by default, with JavaScript only added to interactive components:
---
// Static import, no JS
import StaticCard from '../components/StaticCard.astro';
// Interactive component (can be from React/Vue/Svelte)
import Counter from '../components/Counter.tsx';
---
<!-- Pure static, zero JS -->
<StaticCard title="Statistics" />
<!-- Hydrate on page load -->
<Counter client:load />
<!-- Hydrate when visible (lazy load) -->
<Counter client:visible />
<!-- Hydrate when browser is idle -->
<Counter client:idle />Client Directives:
| Directive | Behavior | Use Case |
|---|---|---|
client:load | Hydrate immediately on page load | Critical interactions |
client:idle | Hydrate when browser is idle | Non-critical interactions |
client:visible | Hydrate when element is visible | Lazy-loaded components |
client:only | Client-side rendering only | Browser API dependent |
client:media | Hydrate when media query matches | Responsive components |
Layout System โ
---
// layouts/DashboardLayout.astro
import Layout from './Layout.astro';
import Sidebar from '../components/dashboard/Sidebar.astro';
import Header from '../components/dashboard/Header.astro';
interface Props {
title: string;
description?: string;
}
const { title, description } = Astro.props;
const currentPath = Astro.url.pathname;
---
<Layout title={title} description={description}>
<div class="min-h-screen bg-gray-50 dark:bg-gray-900">
<Sidebar currentPath={currentPath} />
<div class="lg:pl-64">
<Header title={title} />
<main class="p-4 lg:p-6">
<slot />
</main>
</div>
</div>
</Layout>API Endpoints โ
Astro natively supports creating API endpoints:
// src/pages/api/auth/login.ts
import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request }) => {
const body = await request.json();
const { email, password } = body;
// Validation logic
if (!email || !password) {
return new Response(
JSON.stringify({ success: false, message: 'Email and password are required' }),
{ status: 400, headers: { 'Content-Type': 'application/json' } }
);
}
// Authentication logic...
return new Response(
JSON.stringify({
success: true,
message: 'Login successful',
user: { id: 1, name: 'Admin', role: 'admin' },
token: 'mock_token',
}),
{ status: 200, headers: { 'Content-Type': 'application/json' } }
);
};File-based Routing โ
| File Path | URL | Description |
|---|---|---|
src/pages/index.astro | / | Home |
src/pages/auth/login.astro | /auth/login | Login |
src/pages/dashboard/index.astro | /dashboard | Dashboard |
src/pages/dashboard/[id].astro | /dashboard/:id | Dynamic route |
src/pages/api/auth/login.ts | /api/auth/login | API endpoint |
Page Routes โ
| Path | Page | Permission |
|---|---|---|
/ | Home | Public |
/auth/login | Login | Public |
/auth/register | Register | Public |
/auth/forgot-password | Forgot Password | Public |
/auth/reset-password | Reset Password | Public |
/dashboard | Dashboard | dashboard:view |
/dashboard/analytics | Analytics | analytics:view |
/dashboard/users | User Management | users:view |
/dashboard/accounts | Account Management | accounts:view |
/dashboard/documents | Document Management | documents:view |
/dashboard/files | File Management | files:view |
/dashboard/messages | Message Center | messages:view |
/dashboard/notifications | Notification Center | notifications:view |
/dashboard/calendar | Calendar | calendar:view |
/dashboard/profile | Profile | settings:view |
/dashboard/settings | Settings | settings:view |
/privacy | Privacy Policy | Public |
/terms | Terms of Service | Public |
Environment Variables โ
Configuration โ
# .env
PUBLIC_API_URL=/api
PUBLIC_MOCK=true
PUBLIC_DEMO_EMAIL=admin@halolight.h7ml.cn
PUBLIC_DEMO_PASSWORD=123456
PUBLIC_SHOW_DEMO_HINT=true
PUBLIC_APP_TITLE=Admin Pro
PUBLIC_BRAND_NAME=HalolightVariable Reference โ
| Variable | Description | Default |
|---|---|---|
PUBLIC_API_URL | API base URL | /api |
PUBLIC_MOCK | Enable mock data | true |
PUBLIC_APP_TITLE | App title | Admin Pro |
PUBLIC_BRAND_NAME | Brand name | Halolight |
PUBLIC_DEMO_EMAIL | Demo account email | - |
PUBLIC_DEMO_PASSWORD | Demo account password | - |
PUBLIC_SHOW_DEMO_HINT | Show demo hint | false |
Usage โ
---
// In .astro files
const apiUrl = import.meta.env.PUBLIC_API_URL;
const isMock = import.meta.env.PUBLIC_MOCK === 'true';
---// In .ts files
const apiUrl = import.meta.env.PUBLIC_API_URL;Common Commands โ
# Development
pnpm dev # Start dev server (default port 4321)
pnpm dev --port 3000 # Specify port
# Build
pnpm build # Production build
pnpm preview # Preview production build
# Checks
pnpm astro check # Type check
pnpm lint # ESLint check
pnpm lint:fix # ESLint autofix
# Tests
pnpm test # Run tests
pnpm test:run # Single run
pnpm test:coverage # Coverage report
# Astro CLI
pnpm astro add react # Add React integration
pnpm astro add vue # Add Vue integration
pnpm astro add tailwind # Add Tailwind
pnpm astro add mdx # Add MDX supportTesting โ
# Run tests
pnpm test
# Generate coverage report
pnpm test --coverageTesting Examples โ
// tests/components/Counter.test.tsx
import { describe, it, expect } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import Counter from '../../src/components/Counter';
describe('Counter', () => {
it('renders with initial count', () => {
render(<Counter />);
expect(screen.getByText('0')).toBeInTheDocument();
});
it('increments count on button click', () => {
render(<Counter />);
const button = screen.getByRole('button');
fireEvent.click(button);
expect(screen.getByText('1')).toBeInTheDocument();
});
});Configuration โ
Astro Configuration โ
// astro.config.mjs
import { defineConfig } from 'astro/config';
import tailwind from '@astrojs/tailwind';
import node from '@astrojs/node';
export default defineConfig({
integrations: [tailwind()],
output: 'server', // SSR mode
adapter: node({
mode: 'standalone',
}),
server: {
port: 4321,
host: true,
},
});Output Modes โ
| Mode | Description | Use Case |
|---|---|---|
static | Static site generation (SSG) | Blogs, documentation |
server | Server-side rendering (SSR) | Dynamic applications |
hybrid | Hybrid mode | Partially dynamic |
Deployment โ
Vercel (Recommended) โ
# Install adapter
pnpm add @astrojs/vercel
# astro.config.mjs
import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
output: 'server',
adapter: vercel(),
});Docker โ
FROM node:20-alpine AS builder
RUN npm install -g pnpm
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
CMD ["node", "./dist/server/entry.mjs"]Other Platforms โ
CI/CD โ
Complete GitHub Actions CI workflow configuration:
# .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 astro 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=highAdvanced Features โ
Content Collections โ
Astro's built-in content management system with type-safe Markdown/MDX content.
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blogCollection = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.date(),
author: z.string(),
tags: z.array(z.string()).optional(),
image: z.string().optional(),
}),
});
export const collections = {
blog: blogCollection,
};---
// src/pages/blog/[...slug].astro
import { getCollection } from 'astro:content';
import BlogLayout from '../../layouts/BlogLayout.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<BlogLayout title={post.data.title}>
<article>
<h1>{post.data.title}</h1>
<time>{post.data.pubDate.toLocaleDateString()}</time>
<Content />
</article>
</BlogLayout>View Transitions โ
Native View Transitions API support for smooth page animations.
---
// src/layouts/Layout.astro
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html>---
// Custom transition animations
---
<div transition:name="hero">
<h1 transition:animate="slide">Welcome</h1>
</div>
<style>
/* Custom animations */
@keyframes slide-in {
from { transform: translateX(-100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
::view-transition-old(hero) {
animation: slide-out 0.3s ease-out;
}
::view-transition-new(hero) {
animation: slide-in 0.3s ease-out;
}
</style>Middleware โ
Request interception and processing.
// src/middleware.ts
import { defineMiddleware, sequence } from 'astro:middleware';
// Authentication middleware
const auth = defineMiddleware(async (context, next) => {
const token = context.cookies.get('token')?.value;
// Protected routes
const protectedPaths = ['/dashboard', '/profile', '/settings'];
const isProtected = protectedPaths.some(path =>
context.url.pathname.startsWith(path)
);
if (isProtected && !token) {
return context.redirect('/auth/login');
}
// Pass user info to pages
if (token) {
context.locals.user = await verifyToken(token);
}
return next();
});
// Logger middleware
const logger = defineMiddleware(async (context, next) => {
const start = Date.now();
const response = await next();
const duration = Date.now() - start;
console.log(`${context.request.method} ${context.url.pathname} - ${duration}ms`);
return response;
});
// Compose middleware
export const onRequest = sequence(logger, auth);Performance Optimization โ
Image Optimization โ
---
import { Image } from 'astro:assets';
import myImage from '../assets/hero.png';
---
<!-- Auto-optimized images -->
<Image src={myImage} alt="Hero" width={800} height={600} />
<!-- Remote images -->
<Image
src="https://example.com/image.jpg"
alt="Remote"
width={400}
height={300}
inferSize
/>Lazy Loading Components โ
---
// Use client:visible for lazy loading
import HeavyComponent from '../components/HeavyComponent';
---
<!-- Load only when element is visible -->
<HeavyComponent client:visible />Preload โ
---
// Preload critical resources
---
<head>
<link rel="preload" href="/fonts/inter.woff2" as="font" crossorigin />
<link rel="preconnect" href="https://api.example.com" />
<link rel="dns-prefetch" href="https://cdn.example.com" />
</head>Code Splitting โ
---
// Dynamically import heavy components
const Chart = await import('../components/Chart.tsx');
---
<Chart.default client:visible data={data} />FAQ โ
Q: How do I share state between Islands? โ
A: Use nanostores or Zustand:
pnpm add nanostores @nanostores/react// src/stores/counter.ts
import { atom } from 'nanostores';
export const $counter = atom(0);
export function increment() {
$counter.set($counter.get() + 1);
}// React component
import { useStore } from '@nanostores/react';
import { $counter, increment } from '../stores/counter';
export function Counter() {
const count = useStore($counter);
return <button onClick={increment}>{count}</button>;
}Q: How do I handle form submissions? โ
A: Use API endpoints:
---
// src/pages/contact.astro
---
<form method="POST" action="/api/contact">
<input name="email" type="email" required />
<textarea name="message" required></textarea>
<button type="submit">Submit</button>
</form>// src/pages/api/contact.ts
import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request }) => {
const data = await request.formData();
const email = data.get('email');
const message = data.get('message');
// Handle form data
await sendEmail({ email, message });
return new Response(JSON.stringify({ success: true }), {
status: 200,
headers: { 'Content-Type': 'application/json' },
});
};Q: How do I implement authentication? โ
A: Use middleware + Cookies:
// src/middleware.ts
export const onRequest = defineMiddleware(async (context, next) => {
const token = context.cookies.get('auth-token')?.value;
if (context.url.pathname.startsWith('/dashboard') && !token) {
return context.redirect('/auth/login');
}
if (token) {
try {
const user = await verifyToken(token);
context.locals.user = user;
} catch {
context.cookies.delete('auth-token');
return context.redirect('/auth/login');
}
}
return next();
});Q: What if the bundle size is too large? โ
A: Optimization suggestions:
- Check
client:directive usage, preferclient:visibleorclient:idle - Use dynamic imports
- Remove unused integrations
- Use
@playform/compressto compress output
pnpm add @playform/compress// astro.config.mjs
import compress from '@playform/compress';
export default defineConfig({
integrations: [compress()],
});Comparison with Other Versions โ
| Feature | Astro | Next.js | Vue |
|---|---|---|---|
| Default JS Size | 0 KB | ~80 KB | ~70 KB |
| Islands Architecture | Native support | Not supported | Not supported (Nuxt) |
| Multi-framework Components | Supported | Not supported | Not supported |
| SSG/SSR | Supported | Supported | Supported (Nuxt) |
| Learning Curve | Low | Medium | Medium |