Skip to content

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 โ€‹

TechnologyVersionDescription
Astro5.xIslands architecture framework
TypeScript5.xType safety
Tailwind CSS3.xAtomic CSS
ViteBuilt-inBuild tool
@astrojs/node9.xNode.js adapter
Vitest4.xUnit 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.json

Quick Start โ€‹

Environment Requirements โ€‹

  • Node.js >= 18.0.0
  • pnpm >= 9.x

Installation โ€‹

bash
git clone https://github.com/halolight/halolight-astro.git
cd halolight-astro
pnpm install

Environment Variables โ€‹

bash
cp .env.example .env.local
env
# .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=Halolight

Start Development โ€‹

bash
pnpm dev

Visit http://localhost:4321

Build for Production โ€‹

bash
pnpm build
pnpm preview

Demo Account โ€‹

RoleEmailPassword
Adminadmin@halolight.h7ml.cn123456
Useruser@halolight.h7ml.cn123456

Core Functionality โ€‹

Islands Architecture โ€‹

Astro's Islands architecture allows pages to be static HTML by default, with JavaScript only added to interactive components:

astro
---
// 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:

DirectiveBehaviorUse Case
client:loadHydrate immediately on page loadCritical interactions
client:idleHydrate when browser is idleNon-critical interactions
client:visibleHydrate when element is visibleLazy-loaded components
client:onlyClient-side rendering onlyBrowser API dependent
client:mediaHydrate when media query matchesResponsive components

Layout System โ€‹

astro
---
// 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:

typescript
// 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 PathURLDescription
src/pages/index.astro/Home
src/pages/auth/login.astro/auth/loginLogin
src/pages/dashboard/index.astro/dashboardDashboard
src/pages/dashboard/[id].astro/dashboard/:idDynamic route
src/pages/api/auth/login.ts/api/auth/loginAPI endpoint

Page Routes โ€‹

PathPagePermission
/HomePublic
/auth/loginLoginPublic
/auth/registerRegisterPublic
/auth/forgot-passwordForgot PasswordPublic
/auth/reset-passwordReset PasswordPublic
/dashboardDashboarddashboard:view
/dashboard/analyticsAnalyticsanalytics:view
/dashboard/usersUser Managementusers:view
/dashboard/accountsAccount Managementaccounts:view
/dashboard/documentsDocument Managementdocuments:view
/dashboard/filesFile Managementfiles:view
/dashboard/messagesMessage Centermessages:view
/dashboard/notificationsNotification Centernotifications:view
/dashboard/calendarCalendarcalendar:view
/dashboard/profileProfilesettings:view
/dashboard/settingsSettingssettings:view
/privacyPrivacy PolicyPublic
/termsTerms of ServicePublic

Environment Variables โ€‹

Configuration โ€‹

bash
# .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=Halolight

Variable Reference โ€‹

VariableDescriptionDefault
PUBLIC_API_URLAPI base URL/api
PUBLIC_MOCKEnable mock datatrue
PUBLIC_APP_TITLEApp titleAdmin Pro
PUBLIC_BRAND_NAMEBrand nameHalolight
PUBLIC_DEMO_EMAILDemo account email-
PUBLIC_DEMO_PASSWORDDemo account password-
PUBLIC_SHOW_DEMO_HINTShow demo hintfalse

Usage โ€‹

astro
---
// In .astro files
const apiUrl = import.meta.env.PUBLIC_API_URL;
const isMock = import.meta.env.PUBLIC_MOCK === 'true';
---
typescript
// In .ts files
const apiUrl = import.meta.env.PUBLIC_API_URL;

Common Commands โ€‹

bash
# 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 support

Testing โ€‹

bash
# Run tests
pnpm test

# Generate coverage report
pnpm test --coverage

Testing Examples โ€‹

typescript
// 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 โ€‹

javascript
// 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 โ€‹

ModeDescriptionUse Case
staticStatic site generation (SSG)Blogs, documentation
serverServer-side rendering (SSR)Dynamic applications
hybridHybrid modePartially dynamic

Deployment โ€‹

Deploy with Vercel

bash
# Install adapter
pnpm add @astrojs/vercel

# astro.config.mjs
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
  output: 'server',
  adapter: vercel(),
});

Docker โ€‹

dockerfile
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:

yaml
# .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=high

Advanced Features โ€‹

Content Collections โ€‹

Astro's built-in content management system with type-safe Markdown/MDX content.

typescript
// 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,
};
astro
---
// 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.

astro
---
// src/layouts/Layout.astro
import { ViewTransitions } from 'astro:transitions';
---

<html>
  <head>
    <ViewTransitions />
  </head>
  <body>
    <slot />
  </body>
</html>
astro
---
// 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.

typescript
// 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 โ€‹

astro
---
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 โ€‹

astro
---
// Use client:visible for lazy loading
import HeavyComponent from '../components/HeavyComponent';
---

<!-- Load only when element is visible -->
<HeavyComponent client:visible />

Preload โ€‹

astro
---
// 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 โ€‹

astro
---
// 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:

bash
pnpm add nanostores @nanostores/react
typescript
// src/stores/counter.ts
import { atom } from 'nanostores';

export const $counter = atom(0);

export function increment() {
  $counter.set($counter.get() + 1);
}
tsx
// 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:

astro
---
// 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>
typescript
// 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:

typescript
// 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:

  1. Check client: directive usage, prefer client:visible or client:idle
  2. Use dynamic imports
  3. Remove unused integrations
  4. Use @playform/compress to compress output
bash
pnpm add @playform/compress
javascript
// astro.config.mjs
import compress from '@playform/compress';

export default defineConfig({
  integrations: [compress()],
});

Comparison with Other Versions โ€‹

FeatureAstroNext.jsVue
Default JS Size0 KB~80 KB~70 KB
Islands ArchitectureNative supportNot supportedNot supported (Nuxt)
Multi-framework ComponentsSupportedNot supportedNot supported
SSG/SSRSupportedSupportedSupported (Nuxt)
Learning CurveLowMediumMedium