A modern, minimalist personal landing page theme built with Astro 5, React 19 and Tailwind CSS
Features • Quick Start • Project Structure • Configuration • Deployment • Contributing
| Feature | Description |
|---|---|
| 🌐 Internationalization | Built-in i18n with Astro's native routing and Content Collections with TypeScript validation |
| 🌙 Dark Mode | Automatic theme switching with system preference detection and localStorage persistence |
| 📱 Responsive Design | Mobile-first design optimized for all devices |
| 📰 RSS Aggregation | Advanced RSS/Atom feed system with multiple parsers, automatic image generation, and pre-build fetching |
| 📧 Contact Form | Pre-configured EmailJS integration with form validation |
| 📊 Analytics | Optional Google Analytics 4 integration |
| 🏝️ Islands Architecture | React components loaded on demand, minimizing JS bundle size |
| 🔍 SEO Optimized | Auto-generated sitemap.xml and robots.txt with i18n support |
| ⚡ Fast Performance | Static site generation with optimized assets and image processing |
| 🖼️ Image Optimization | Astro's Image API integration with responsive image generation |
| 📝 Content Validation | Zod schema validation for all content with TypeScript type safety |
| 🔄 Automated RSS Fetching | Pre-build RSS fetching with retry logic and error handling |
# Clone the repository
git clone https://github.com/WayneXuCN/LandingPage.git my-site
cd my-site
# Install dependencies
bun install
# Start development server
bun run devOpen http://localhost:4321 to see your site.
# Build the site (RSS feeds are automatically fetched)
bun run build
# Preview the production build
bun run previewOutput is generated in the dist/ directory, ready for deployment.
src/
├── components/
│ ├── react/ # React Islands (interactive components)
│ │ ├── Contact.jsx # Contact form with EmailJS
│ │ ├── ErrorBoundary.jsx # React error boundary
│ │ ├── HeaderBar.jsx # Navigation header with language switcher
│ │ ├── LanguageSwitcher.jsx # Language selector component
│ │ ├── PrimaryNav.jsx # Primary navigation menu
│ │ ├── ThemeToggle.jsx # Dark/light mode toggle
│ │ └── UnderlineEffects.jsx # Underline hover effects
│ │
│ └── ui/ # Astro Components (static components)
│ ├── Hero.astro # Hero section
│ ├── Footer.astro # Site footer
│ ├── WebsiteItem.astro # Website showcase item
│ ├── WebsitesSection.astro # Websites showcase section
│ ├── FeaturedPostItem.astro # Featured post item
│ └── FeaturedPostsSection.astro # Featured posts section
│
├── layouts/
│ └── BaseLayout.astro # Global layout with SEO and meta tags
│
├── lib/
│ ├── i18n.ts # Internationalization utilities
│ ├── utils.ts # Common utility functions
│ ├── localImages.ts # Local image processing utilities
│ └── websiteImages.ts # Website image processing utilities
│
├── pages/
│ ├── index.astro # Root homepage (renders Chinese version)
│ ├── 404.astro # Root 404 page
│ └── [lang]/ # Dynamic routes for languages
│ ├── index.astro # Language-specific homepage
│ ├── about.astro # About page
│ ├── contact.astro # Contact page
│ └── 404.astro # Language-specific 404 page
│
├── data/
│ └── rss-posts.json # RSS aggregated posts data (auto-generated)
│
└── styles/
└── global.css # Global styles and custom CSS
i18n/ # Internationalization content
├── zh_CN.json # Chinese content
└── en_US.json # English content
scripts/
└── fetch-rss.bun.js # RSS fetching script with advanced parsing
src/
├── content.config.ts # Content collections schema and validation
└── middleware.ts # Internationalization routing middleware
Create a .env file in the project root:
# EmailJS (required for contact form)
PUBLIC_EMAILJS_SERVICE_ID=your_service_id
PUBLIC_EMAILJS_TEMPLATE_ID=your_template_id
PUBLIC_EMAILJS_PUBLIC_KEY=your_public_key
# Google Analytics (optional)
PUBLIC_GA_ID=G-XXXXXXXXXXMain configuration is in astro.config.mjs:
export default defineConfig({
// Site URL for production
site: 'https://your-domain.com',
// Static site generation
output: 'static',
// Internationalization
i18n: {
defaultLocale: 'zh_CN',
locales: ['zh_CN', 'en_US'],
routing: {
prefixDefaultLocale: true,
redirectToDefaultLocale: false,
},
},
// Integrations
integrations: [
react(), // React support
mdx(), // MDX support
tailwind(), // Tailwind CSS
icon(), // Iconify icons
sitemap({ // Auto-generate sitemap.xml with i18n support
i18n: {
defaultLocale: 'zh_CN',
locales: {
zh_CN: 'zh-CN',
en_US: 'en-US',
},
},
}),
robotsTxt(), // Auto-generate robots.txt
],
// Image optimization
image: {
domains: ['images.unsplash.com', 'unsplash.com', 'picsum.photos'],
remotePatterns: [
{
protocol: 'https',
hostname: '**.unsplash.com',
},
{
protocol: 'https',
hostname: 'picsum.photos',
},
],
},
});All site content is managed through JSON files in i18n/:
| File | Description |
|---|---|
zh_CN.json |
Chinese content |
en_US.json |
English content |
Content structure follows a strict schema defined in src/content.config.ts with Zod validation:
{
"site": {
"title": "Site Title",
"author": "Your Name",
"description": "Site description",
"keywords": "keywords, for, seo"
},
"nav": [
{ "label": "Home", "href": "index.html" },
{ "label": "About", "href": "about.html" },
{ "label": "Contact", "href": "contact.html" }
],
"header": {
"avatar": "/img/prof_pic.png",
"name": "Your Name"
},
"hero": {
"subtitle": "Welcome",
"title": "Your Name",
"description": "Your introduction text..."
},
"websites": {
"title": "My Websites",
"items": [
{
"id": "website-1",
"title": "Website Title",
"image": "/websites/website.png",
"url": "https://example.com",
"description": "Website description"
}
]
},
"featuredPosts": {
"title": "Featured Posts",
"rss": {
"enabled": true,
"feeds": [
{ "url": "https://blog.example.com/feed.xml", "parser": "default" }
],
"limit": 6
},
"items": [],
"seeAllText": "See all posts",
"seeAllUrl": "https://blog.example.com"
},
"footer": {
"copyright": "© 2024 Your Name. All rights reserved.",
"icp": {
"text": "ICP备案号",
"url": "https://beian.miit.gov.cn"
},
"mps": {
"text": "公安备案号",
"url": "https://www.beian.gov.cn",
"logo": "/assets/img/mps-logo.png"
},
"socialLinks": [
{
"icon": "mdi:github",
"url": "https://github.com/yourusername",
"title": "GitHub"
}
]
},
"about": {
"hero": {
"subtitle": "About",
"title": "About Me",
"description": "About me description..."
},
"timeline": {
"subtitle": "Journey",
"title": "My Timeline",
"period": "2018 - Present",
"items": [
{
"period": "2020-2023",
"title": "Job Title",
"description": "Job description..."
}
]
},
"values": {
"subtitle": "Values",
"title": "My Values",
"items": [
{
"label": "Value 1",
"text": "Value description..."
}
],
"product": {
"subtitle": "Product",
"title": "My Product",
"description": "Product description...",
"linkText": "Learn more",
"linkUrl": "https://product.example.com"
}
},
"philosophy": {
"subtitle": "Philosophy",
"title": "My Philosophy",
"description": "Philosophy description...",
"ctaText": "Get in touch",
"ctaUrl": "/contact"
}
},
"contact": {
"hero": {
"subtitle": "Contact",
"title": "Get in Touch",
"description": "Contact description..."
},
"cards": {
"email": {
"subtitle": "Email",
"address": "your.email@example.com",
"note": "Response within 24 hours"
},
"social": {
"subtitle": "Social Media",
"items": [
{
"label": "GitHub",
"url": "https://github.com/yourusername",
"icon": "mdi:github",
"handle": "@yourusername"
}
]
}
},
"form": {
"subtitle": "Send Message",
"title": "Contact Form",
"note": "All fields are required"
},
"actions": {
"writeEmail": "Write Email",
"copy": "Copy",
"copied": "Copied!"
},
"formLabels": {
"name": "Name",
"email": "Email",
"topic": "Topic",
"message": "Message"
},
"formPlaceholders": {
"name": "Your name",
"email": "your.email@example.com",
"message": "Your message..."
},
"formOptions": {
"consulting": "Consulting",
"content": "Content Creation",
"share": "Share Ideas",
"other": "Other"
},
"formSubmit": {
"default": "Send Message",
"sending": "Sending...",
"success": "Message Sent!",
"error": "Error Sending"
},
"services": {
"items": [
{
"subtitle": "Service 1",
"title": "Service Title",
"description": "Service description..."
}
]
}
}
}Configure RSS aggregation in your locale JSON:
{
"featuredPosts": {
"rss": {
"enabled": true,
"feeds": [
{ "url": "https://blog.example.com/feed.xml", "parser": "default" },
{ "url": "https://astro-paper.vercel.app/rss.xml", "parser": "astroPaper" }
],
"limit": 6
}
}
}| Parser | Description | Use Case |
|---|---|---|
default |
Standard RSS/Atom parser | Most RSS feeds |
astroPaper |
Astro Paper theme parser | Blogs using Astro Paper theme |
jekyllFeed |
Jekyll feed parser | Jekyll-based blogs |
- Multi-parser support: Different parsers for different feed formats
- Automatic image generation: Deterministic hash-based images from Picsum
- Retry logic: Exponential backoff for failed requests
- Content validation: Type-safe content processing
- Pre-build fetching: Automatically runs before build
Fetch RSS feeds manually:
bun run fetch:rss-
Update Astro configuration (
astro.config.mjs):i18n: { defaultLocale: 'zh_CN', locales: ['zh_CN', 'en_US', 'NEW_LOCALE'], }, // Update sitemap i18n config sitemap({ i18n: { defaultLocale: 'zh_CN', locales: { zh_CN: 'zh-CN', en_US: 'en-US', NEW_LOCALE: 'new-locale', }, }, }),
-
Update i18n utilities (
src/lib/i18n.ts):export const locales = ['zh_CN', 'en_US', 'NEW_LOCALE'] as const; export const localeConfig: Record<Locale, { label: string; name: string; hrefLang: string }> = { NEW_LOCALE: { label: 'XX', name: 'Language Name', hrefLang: 'new-locale', }, };
-
Create translation file (
i18n/NEW_LOCALE.json): Copyen_US.jsonand translate all content. -
Create page routes (
src/pages/[lang]/): Copy existing language pages and update content references.
- Colors & Theme: Edit
tailwind.config.mjs - Global Styles: Edit
src/styles/global.css - Dark Mode: Use Tailwind's
dark:prefix - Custom Icons: Add to
public/assets/css/custom-icons.css
| Component | Purpose | Client Directive |
|---|---|---|
HeaderBar.jsx |
Navigation with language switcher | client:load |
Contact.jsx |
Contact form with EmailJS | client:load |
ErrorBoundary.jsx |
React error boundary | - |
LanguageSwitcher.jsx |
Language selector | client:load |
PrimaryNav.jsx |
Primary navigation | client:load |
ThemeToggle.jsx |
Dark/light mode toggle | client:load |
UnderlineEffects.jsx |
Underline hover effects | client:idle |
Set these environment variables in your hosting platform:
# EmailJS (required for contact form)
PUBLIC_EMAILJS_SERVICE_ID=your_service_id
PUBLIC_EMAILJS_TEMPLATE_ID=your_template_id
PUBLIC_EMAILJS_PUBLIC_KEY=your_public_key
# Google Analytics (optional)
PUBLIC_GA_ID=G-XXXXXXXXXX| Command | Description |
|---|---|
bun run dev |
Start development server (Bun runtime) |
bun run dev:node |
Start development server (Node.js runtime) |
bun run build |
Build for production (Bun runtime) |
bun run build:node |
Build for production (Node.js runtime) |
bun run preview |
Preview production build |
bun run fetch:rss |
Fetch RSS feeds manually |
bun run prebuild |
Pre-build RSS fetching (runs automatically before build) |
bun run format |
Format code with Prettier |
bun run format:check |
Check code formatting |
bun run type-check |
TypeScript type checking |
bun run lint |
Astro code linting |
bun run clean |
Clean build artifacts |
bun run reinstall |
Clean and reinstall dependencies |
- Framework: Astro 5.x
- UI: React 19.x
- Styling: Tailwind CSS 3.x
- Runtime: Bun 1.x (recommended) or Node.js 18+
- Email: EmailJS
- Icons: Iconify (Material Design Icons)
- Content: Astro Content Collections with TypeScript validation
- SEO: Auto-generated sitemap.xml and robots.txt with i18n support
- Images: Astro Image API with responsive optimization
Contributions are welcome! Please read our Contributing Guide before submitting a PR.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project is licensed under the MIT License.
Made with ❤️ by Wenjie Xu


