first commit
This commit is contained in:
160
.gitignore
vendored
Normal file
160
.gitignore
vendored
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/node,hugo
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=node,hugo
|
||||||
|
|
||||||
|
### Hugo ###
|
||||||
|
# Generated files by hugo
|
||||||
|
/public/
|
||||||
|
/resources/_gen/
|
||||||
|
/assets/jsconfig.json
|
||||||
|
hugo_stats.json
|
||||||
|
|
||||||
|
# Executable may be added to repository
|
||||||
|
hugo.exe
|
||||||
|
hugo.darwin
|
||||||
|
hugo.linux
|
||||||
|
|
||||||
|
# Temporary lock file while building
|
||||||
|
/.hugo_build.lock
|
||||||
|
|
||||||
|
### Node ###
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
### Node Patch ###
|
||||||
|
# Serverless Webpack directories
|
||||||
|
.webpack/
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
# SvelteKit build / generate output
|
||||||
|
.svelte-kit
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/node,hugo
|
||||||
|
|
||||||
5
archetypes/default.md
Normal file
5
archetypes/default.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
+++
|
||||||
|
date = '{{ .Date }}'
|
||||||
|
draft = true
|
||||||
|
title = '{{ replace .File.ContentBaseName "-" " " | title }}'
|
||||||
|
+++
|
||||||
400
docs/hugo-wordpress-integration.md
Normal file
400
docs/hugo-wordpress-integration.md
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
# Hugo WordPress Integration Guide
|
||||||
|
|
||||||
|
This guide provides step-by-step instructions for setting up a Hugo static site that fetches and displays blog articles from a WordPress API.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Your Hugo project will act as a static site generator that pulls content from a WordPress REST API and generates a fast, SEO-friendly blog. This approach combines the performance benefits of static sites with the content management capabilities of WordPress.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Hugo installed (v0.110.0 or later)
|
||||||
|
- WordPress site with REST API enabled
|
||||||
|
- Basic knowledge of Go templates and Hugo
|
||||||
|
- Node.js (for build scripts)
|
||||||
|
|
||||||
|
## Step 1: Configure Hugo for WordPress API Integration
|
||||||
|
|
||||||
|
### Update hugo.toml Configuration
|
||||||
|
|
||||||
|
Add the following configuration to your [`hugo.toml`](hugo.toml:1) file:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
# Base configuration
|
||||||
|
baseURL = "https://yourblog.com"
|
||||||
|
languageCode = "en-us"
|
||||||
|
title = "Your Blog Title"
|
||||||
|
theme = "your-theme"
|
||||||
|
|
||||||
|
# WordPress API Configuration
|
||||||
|
[params.wordpress]
|
||||||
|
apiUrl = "https://your-wordpress-site.com/wp-json/wp/v2"
|
||||||
|
postsPerPage = 10
|
||||||
|
featuredImageSize = "large"
|
||||||
|
authorEndpoint = "users"
|
||||||
|
categoryEndpoint = "categories"
|
||||||
|
tagEndpoint = "tags"
|
||||||
|
|
||||||
|
# Build configuration
|
||||||
|
[build]
|
||||||
|
writeStats = true
|
||||||
|
[[build.cachebusters]]
|
||||||
|
source = "assets/.*\\.(js|ts|jsx|ts)$"
|
||||||
|
target = "js"
|
||||||
|
[[build.cachebusters]]
|
||||||
|
source = "assets/.*\\.(css|sass|scss)$"
|
||||||
|
target = "css"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 2: Create Data Fetching Scripts
|
||||||
|
|
||||||
|
### Create API Fetching Script
|
||||||
|
|
||||||
|
Create a new file [`scripts/fetch-wordpress.js`](scripts/fetch-wordpress.js:1):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
const WORDPRESS_API = 'https://www.mistergeek.net//wp-json/wp/v2';
|
||||||
|
const OUTPUT_DIR = path.join(__dirname, '..', 'data', 'wordpress');
|
||||||
|
|
||||||
|
async function fetchPosts(page = 1, perPage = 100) {
|
||||||
|
const response = await fetch(`${WORDPRESS_API}/posts?page=${page}&per_page=${perPage}&_embed`);
|
||||||
|
const posts = await response.json();
|
||||||
|
|
||||||
|
if (response.headers.get('x-wp-totalpages') > page) {
|
||||||
|
const nextPosts = await fetchPosts(page + 1, perPage);
|
||||||
|
return [...posts, ...nextPosts];
|
||||||
|
}
|
||||||
|
|
||||||
|
return posts;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchCategories() {
|
||||||
|
const response = await fetch(`${WORDPRESS_API}/categories?per_page=100`);
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchTags() {
|
||||||
|
const response = await fetch(`${WORDPRESS_API}/tags?per_page=100`);
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAuthors() {
|
||||||
|
const response = await fetch(`${WORDPRESS_API}/users?per_page=100`);
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateData() {
|
||||||
|
// Ensure output directory exists
|
||||||
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||||
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Fetching WordPress data...');
|
||||||
|
|
||||||
|
const [posts, categories, tags, authors] = await Promise.all([
|
||||||
|
fetchPosts(),
|
||||||
|
fetchCategories(),
|
||||||
|
fetchTags(),
|
||||||
|
fetchAuthors()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Save data as JSON files
|
||||||
|
fs.writeFileSync(path.join(OUTPUT_DIR, 'posts.json'), JSON.stringify(posts, null, 2));
|
||||||
|
fs.writeFileSync(path.join(OUTPUT_DIR, 'categories.json'), JSON.stringify(categories, null, 2));
|
||||||
|
fs.writeFileSync(path.join(OUTPUT_DIR, 'tags.json'), JSON.stringify(tags, null, 2));
|
||||||
|
fs.writeFileSync(path.join(OUTPUT_DIR, 'authors.json'), JSON.stringify(authors, null, 2));
|
||||||
|
|
||||||
|
console.log(`✅ Fetched ${posts.length} posts, ${categories.length} categories, ${tags.length} tags, ${authors.length} authors`);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateData().catch(console.error);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create Package.json for Build Scripts
|
||||||
|
|
||||||
|
Create [`package.json`](package.json:1):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "hugo-wordpress-blog",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Hugo static site with WordPress content",
|
||||||
|
"scripts": {
|
||||||
|
"fetch-data": "node scripts/fetch-wordpress.js",
|
||||||
|
"build": "npm run fetch-data && hugo --minify",
|
||||||
|
"dev": "npm run fetch-data && hugo server -D"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "^3.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 3: Create Hugo Templates for WordPress Content
|
||||||
|
|
||||||
|
### Create Content Layout
|
||||||
|
|
||||||
|
Create [`layouts/_default/single.html`](layouts/_default/single.html:1):
|
||||||
|
|
||||||
|
```html
|
||||||
|
{{ define "main" }}
|
||||||
|
<article class="post">
|
||||||
|
<header class="post-header">
|
||||||
|
<h1 class="post-title">{{ .Title }}</h1>
|
||||||
|
<div class="post-meta">
|
||||||
|
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "January 2, 2006" }}</time>
|
||||||
|
{{ if .Params.author }}
|
||||||
|
<span class="post-author">{{ .Params.author.name }}</span>
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Params.categories }}
|
||||||
|
<div class="post-categories">
|
||||||
|
{{ range .Params.categories }}
|
||||||
|
<a href="/categories/{{ .slug }}" class="category">{{ .name }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ if .Params.featured_image }}
|
||||||
|
<img src="{{ .Params.featured_image }}" alt="{{ .Title }}" class="featured-image">
|
||||||
|
{{ end }}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="post-content">
|
||||||
|
{{ .Content }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ if .Params.tags }}
|
||||||
|
<div class="post-tags">
|
||||||
|
{{ range .Params.tags }}
|
||||||
|
<a href="/tags/{{ .slug }}" class="tag">#{{ .name }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</article>
|
||||||
|
{{ end }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create List Layout
|
||||||
|
|
||||||
|
Create [`layouts/_default/list.html`](layouts/_default/list.html:1):
|
||||||
|
|
||||||
|
```html
|
||||||
|
{{ define "main" }}
|
||||||
|
<div class="posts-list">
|
||||||
|
<h1>{{ .Title }}</h1>
|
||||||
|
|
||||||
|
{{ range .Pages }}
|
||||||
|
<article class="post-preview">
|
||||||
|
<h2><a href="{{ .RelPermalink }}">{{ .Title }}</a></h2>
|
||||||
|
<div class="post-meta">
|
||||||
|
<time>{{ .Date.Format "Jan 2, 2006" }}</time>
|
||||||
|
{{ if .Params.author }}
|
||||||
|
<span>{{ .Params.author.name }}</span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ if .Params.excerpt }}
|
||||||
|
<p class="post-excerpt">{{ .Params.excerpt }}</p>
|
||||||
|
{{ end }}
|
||||||
|
<a href="{{ .RelPermalink }}" class="read-more">Read more →</a>
|
||||||
|
</article>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 4: Create Hugo Content from WordPress Data
|
||||||
|
|
||||||
|
### Create Content Generator Script
|
||||||
|
|
||||||
|
Create [`scripts/generate-content.js`](scripts/generate-content.js:1):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const DATA_DIR = path.join(__dirname, '..', 'data', 'wordpress');
|
||||||
|
const CONTENT_DIR = path.join(__dirname, '..', 'content', 'posts');
|
||||||
|
|
||||||
|
function generateContent() {
|
||||||
|
const posts = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'posts.json'), 'utf8'));
|
||||||
|
|
||||||
|
// Ensure content directory exists
|
||||||
|
if (!fs.existsSync(CONTENT_DIR)) {
|
||||||
|
fs.mkdirSync(CONTENT_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
posts.forEach(post => {
|
||||||
|
const slug = post.slug;
|
||||||
|
const date = new Date(post.date);
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
|
||||||
|
const contentDir = path.join(CONTENT_DIR, `${year}-${month}-${slug}`);
|
||||||
|
|
||||||
|
if (!fs.existsSync(contentDir)) {
|
||||||
|
fs.mkdirSync(contentDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const frontmatter = {
|
||||||
|
title: post.title.rendered,
|
||||||
|
date: post.date,
|
||||||
|
draft: post.status !== 'publish',
|
||||||
|
slug: slug,
|
||||||
|
wordpress_id: post.id,
|
||||||
|
excerpt: post.excerpt.rendered.replace(/<[^>]*>/g, ''),
|
||||||
|
featured_image: post._embedded?.['wp:featuredmedia']?.[0]?.source_url || '',
|
||||||
|
author: {
|
||||||
|
id: post.author,
|
||||||
|
name: post._embedded?.author?.[0]?.name || 'Unknown'
|
||||||
|
},
|
||||||
|
categories: post._embedded?.['wp:term']?.[0]?.map(cat => ({
|
||||||
|
id: cat.id,
|
||||||
|
name: cat.name,
|
||||||
|
slug: cat.slug
|
||||||
|
})) || [],
|
||||||
|
tags: post._embedded?.['wp:term']?.[1]?.map(tag => ({
|
||||||
|
id: tag.id,
|
||||||
|
name: tag.name,
|
||||||
|
slug: tag.slug
|
||||||
|
})) || []
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = `---
|
||||||
|
${Object.entries(frontmatter)
|
||||||
|
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
||||||
|
.join('\n')}
|
||||||
|
---
|
||||||
|
|
||||||
|
${post.content.rendered}`;
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(contentDir, 'index.md'), content);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Generated ${posts.length} content files`);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateContent();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 5: Update Build Process
|
||||||
|
|
||||||
|
### Create Build Script
|
||||||
|
|
||||||
|
Update your [`package.json`](package.json:1) build scripts:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"fetch-data": "node scripts/fetch-wordpress.js",
|
||||||
|
"generate-content": "node scripts/generate-content.js",
|
||||||
|
"prebuild": "npm run fetch-data && npm run generate-content",
|
||||||
|
"build": "hugo --minify",
|
||||||
|
"dev": "npm run fetch-data && npm run generate-content && hugo server -D",
|
||||||
|
"clean": "rm -rf data/wordpress content/posts public"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 6: Configure Netlify for Automated Builds
|
||||||
|
|
||||||
|
Create [`netlify.toml`](netlify.toml:1):
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[build]
|
||||||
|
command = "npm run build"
|
||||||
|
publish = "public"
|
||||||
|
|
||||||
|
[build.environment]
|
||||||
|
HUGO_VERSION = "0.110.0"
|
||||||
|
NODE_VERSION = "18"
|
||||||
|
|
||||||
|
[[plugins]]
|
||||||
|
package = "@netlify/plugin-sitemap"
|
||||||
|
|
||||||
|
[[plugins]]
|
||||||
|
package = "netlify-plugin-hugo-cache-resources"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 7: Advanced Features
|
||||||
|
|
||||||
|
### Add Search Functionality
|
||||||
|
|
||||||
|
Create [`layouts/partials/search.html`](layouts/partials/search.html:1):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="search-container">
|
||||||
|
<input type="search" id="search" placeholder="Search articles..." aria-label="Search">
|
||||||
|
<div id="search-results" class="search-results"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Add client-side search using Fuse.js
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add RSS Feed
|
||||||
|
|
||||||
|
Create [`layouts/index.rss.xml`](layouts/index.rss.xml:1):
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<rss version="2.0">
|
||||||
|
<channel>
|
||||||
|
<title>{{ .Site.Title }}</title>
|
||||||
|
<link>{{ .Site.BaseURL }}</link>
|
||||||
|
<description>{{ .Site.Params.description }}</description>
|
||||||
|
<language>{{ .Site.LanguageCode }}</language>
|
||||||
|
{{ range first 15 .Pages }}
|
||||||
|
<item>
|
||||||
|
<title>{{ .Title }}</title>
|
||||||
|
<link>{{ .Permalink }}</link>
|
||||||
|
<description>{{ .Params.excerpt }}</description>
|
||||||
|
<pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" }}</pubDate>
|
||||||
|
<guid>{{ .Permalink }}</guid>
|
||||||
|
</item>
|
||||||
|
{{ end }}
|
||||||
|
</channel>
|
||||||
|
</rss>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment Options
|
||||||
|
|
||||||
|
### Netlify
|
||||||
|
1. Connect your GitHub repository
|
||||||
|
2. Set build command: `npm run build`
|
||||||
|
3. Set publish directory: `public`
|
||||||
|
|
||||||
|
### Vercel
|
||||||
|
1. Install Vercel CLI: `npm i -g vercel`
|
||||||
|
2. Run: `vercel --prod`
|
||||||
|
|
||||||
|
### GitHub Pages
|
||||||
|
1. Create `.github/workflows/deploy.yml`
|
||||||
|
2. Use GitHub Actions for automated deployment
|
||||||
|
|
||||||
|
## Performance Optimization
|
||||||
|
|
||||||
|
1. **Image Optimization**: Use Hugo's image processing
|
||||||
|
2. **CDN**: Configure Cloudflare or similar
|
||||||
|
3. **Caching**: Implement proper cache headers
|
||||||
|
4. **Minification**: Enable HTML/CSS/JS minification
|
||||||
|
5. **Preloading**: Add resource hints for critical assets
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. Run `npm install` to install dependencies
|
||||||
|
2. Run `npm run dev` to start development server
|
||||||
|
3. Customize your theme and styling
|
||||||
|
4. Set up automated deployment
|
||||||
|
5. Configure SEO meta tags
|
||||||
|
6. Add analytics (Google Analytics, Plausible, etc.)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
- **API Rate Limits**: Implement caching and rate limiting
|
||||||
|
- **Image Loading**: Ensure WordPress images are accessible
|
||||||
|
- **CORS Issues**: Configure WordPress CORS headers
|
||||||
|
- **Build Time**: Consider incremental builds for large sites
|
||||||
17
docs/package.json
Normal file
17
docs/package.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "hugo-wordpress-blog",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Hugo static site with WordPress content",
|
||||||
|
{
|
||||||
|
"scripts": {
|
||||||
|
"fetch-data": "node scripts/fetch-wordpress.js",
|
||||||
|
"generate-content": "node scripts/generate-content.js",
|
||||||
|
"prebuild": "npm run fetch-data && npm run generate-content",
|
||||||
|
"build": "hugo --minify",
|
||||||
|
"dev": "npm run fetch-data && npm run generate-content && hugo server -D",
|
||||||
|
"clean": "rm -rf data/wordpress content/posts public"
|
||||||
|
}
|
||||||
|
"dependencies": {
|
||||||
|
"node-fetch": "^3.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
23
hugo.toml
Normal file
23
hugo.toml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
baseURL = 'https://www.mistergeek.net/'
|
||||||
|
languageCode = 'fr-fr'
|
||||||
|
title = 'Mistergeek'
|
||||||
|
# theme = "your-theme"
|
||||||
|
|
||||||
|
# WordPress API Configuration
|
||||||
|
[params.wordpress]
|
||||||
|
apiUrl = "https://www.mistergeek.net/wp-json/wp/v2"
|
||||||
|
postsPerPage = 10
|
||||||
|
featuredImageSize = "large"
|
||||||
|
authorEndpoint = "users"
|
||||||
|
categoryEndpoint = "categories"
|
||||||
|
tagEndpoint = "tags"
|
||||||
|
|
||||||
|
# Build configuration
|
||||||
|
[build]
|
||||||
|
writeStats = true
|
||||||
|
[[build.cachebusters]]
|
||||||
|
source = "assets/.*\\.(js|ts|jsx|ts)$"
|
||||||
|
target = "js"
|
||||||
|
[[build.cachebusters]]
|
||||||
|
source = "assets/.*\\.(css|sass|scss)$"
|
||||||
|
target = "css"
|
||||||
20
layouts/_default/list.html
Normal file
20
layouts/_default/list.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<div class="posts-list">
|
||||||
|
<h1>{{ .Title }}</h1>
|
||||||
|
|
||||||
|
{{ range .Pages }}
|
||||||
|
<article class="post-preview">
|
||||||
|
<h2><a href="{{ .RelPermalink }}">{{ .Title }}</a></h2>
|
||||||
|
<div class="post-meta">
|
||||||
|
<time>{{ .Date.Format "Jan 2, 2006" }}</time>
|
||||||
|
{{ if .Params.author }}
|
||||||
|
<span>{{ .Params.author.name }}</span>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ if .Params.excerpt }}
|
||||||
|
<p class="post-excerpt">{{ .Params.excerpt }}</p>
|
||||||
|
{{ end }}
|
||||||
|
<a href="{{ .RelPermalink }}" class="read-more">Read more →</a>
|
||||||
|
</article>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
35
layouts/_default/single.html
Normal file
35
layouts/_default/single.html
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{{ define "main" }}
|
||||||
|
<article class="post">
|
||||||
|
<header class="post-header">
|
||||||
|
<h1 class="post-title">{{ .Title }}</h1>
|
||||||
|
<div class="post-meta">
|
||||||
|
<time datetime="{{ .Date.Format "2006-01-02" }}">{{ .Date.Format "January 2, 2006" }}</time>
|
||||||
|
{{ if .Params.author }}
|
||||||
|
<span class="post-author">{{ .Params.author.name }}</span>
|
||||||
|
{{ end }}
|
||||||
|
{{ if .Params.categories }}
|
||||||
|
<div class="post-categories">
|
||||||
|
{{ range .Params.categories }}
|
||||||
|
<a href="/categories/{{ .slug }}" class="category">{{ .name }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ if .Params.featured_image }}
|
||||||
|
<img src="{{ .Params.featured_image }}" alt="{{ .Title }}" class="featured-image">
|
||||||
|
{{ end }}
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="post-content">
|
||||||
|
{{ .Content }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ if .Params.tags }}
|
||||||
|
<div class="post-tags">
|
||||||
|
{{ range .Params.tags }}
|
||||||
|
<a href="/tags/{{ .slug }}" class="tag">#{{ .name }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
</article>
|
||||||
|
{{ end }}
|
||||||
59
scripts/fetch-wordpress.js
Normal file
59
scripts/fetch-wordpress.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const fetch = require('node-fetch');
|
||||||
|
|
||||||
|
const WORDPRESS_API = 'https://www.mistergeek.net/wp-json/wp/v2';
|
||||||
|
const OUTPUT_DIR = path.join(__dirname, '..', 'data', 'wordpress');
|
||||||
|
|
||||||
|
async function fetchPosts(page = 1, perPage = 100) {
|
||||||
|
const response = await fetch(`${WORDPRESS_API}/posts?page=${page}&per_page=${perPage}&_embed`);
|
||||||
|
const posts = await response.json();
|
||||||
|
|
||||||
|
if (response.headers.get('x-wp-totalpages') > page) {
|
||||||
|
const nextPosts = await fetchPosts(page + 1, perPage);
|
||||||
|
return [...posts, ...nextPosts];
|
||||||
|
}
|
||||||
|
|
||||||
|
return posts;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchCategories() {
|
||||||
|
const response = await fetch(`${WORDPRESS_API}/categories?per_page=100`);
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchTags() {
|
||||||
|
const response = await fetch(`${WORDPRESS_API}/tags?per_page=100`);
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAuthors() {
|
||||||
|
const response = await fetch(`${WORDPRESS_API}/users?per_page=100`);
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateData() {
|
||||||
|
// Ensure output directory exists
|
||||||
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
||||||
|
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Fetching WordPress data...');
|
||||||
|
|
||||||
|
const [posts, categories, tags, authors] = await Promise.all([
|
||||||
|
fetchPosts(),
|
||||||
|
fetchCategories(),
|
||||||
|
fetchTags(),
|
||||||
|
fetchAuthors()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Save data as JSON files
|
||||||
|
fs.writeFileSync(path.join(OUTPUT_DIR, 'posts.json'), JSON.stringify(posts, null, 2));
|
||||||
|
fs.writeFileSync(path.join(OUTPUT_DIR, 'categories.json'), JSON.stringify(categories, null, 2));
|
||||||
|
fs.writeFileSync(path.join(OUTPUT_DIR, 'tags.json'), JSON.stringify(tags, null, 2));
|
||||||
|
fs.writeFileSync(path.join(OUTPUT_DIR, 'authors.json'), JSON.stringify(authors, null, 2));
|
||||||
|
|
||||||
|
console.log(`✅ Fetched ${posts.length} posts, ${categories.length} categories, ${tags.length} tags, ${authors.length} authors`);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateData().catch(console.error);
|
||||||
65
scripts/generate-content.js
Normal file
65
scripts/generate-content.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const DATA_DIR = path.join(__dirname, '..', 'data', 'wordpress');
|
||||||
|
const CONTENT_DIR = path.join(__dirname, '..', 'content', 'posts');
|
||||||
|
|
||||||
|
function generateContent() {
|
||||||
|
const posts = JSON.parse(fs.readFileSync(path.join(DATA_DIR, 'posts.json'), 'utf8'));
|
||||||
|
|
||||||
|
// Ensure content directory exists
|
||||||
|
if (!fs.existsSync(CONTENT_DIR)) {
|
||||||
|
fs.mkdirSync(CONTENT_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
posts.forEach(post => {
|
||||||
|
const slug = post.slug;
|
||||||
|
const date = new Date(post.date);
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
|
||||||
|
const contentDir = path.join(CONTENT_DIR, `${year}-${month}-${slug}`);
|
||||||
|
|
||||||
|
if (!fs.existsSync(contentDir)) {
|
||||||
|
fs.mkdirSync(contentDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const frontmatter = {
|
||||||
|
title: post.title.rendered,
|
||||||
|
date: post.date,
|
||||||
|
draft: post.status !== 'publish',
|
||||||
|
slug: slug,
|
||||||
|
wordpress_id: post.id,
|
||||||
|
excerpt: post.excerpt.rendered.replace(/<[^>]*>/g, ''),
|
||||||
|
featured_image: post._embedded?.['wp:featuredmedia']?.[0]?.source_url || '',
|
||||||
|
author: {
|
||||||
|
id: post.author,
|
||||||
|
name: post._embedded?.author?.[0]?.name || 'Unknown'
|
||||||
|
},
|
||||||
|
categories: post._embedded?.['wp:term']?.[0]?.map(cat => ({
|
||||||
|
id: cat.id,
|
||||||
|
name: cat.name,
|
||||||
|
slug: cat.slug
|
||||||
|
})) || [],
|
||||||
|
tags: post._embedded?.['wp:term']?.[1]?.map(tag => ({
|
||||||
|
id: tag.id,
|
||||||
|
name: tag.name,
|
||||||
|
slug: tag.slug
|
||||||
|
})) || []
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = `---
|
||||||
|
${Object.entries(frontmatter)
|
||||||
|
.map(([key, value]) => `${key}: ${JSON.stringify(value)}`)
|
||||||
|
.join('\n')}
|
||||||
|
---
|
||||||
|
|
||||||
|
${post.content.rendered}`;
|
||||||
|
|
||||||
|
fs.writeFileSync(path.join(contentDir, 'index.md'), content);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`✅ Generated ${posts.length} content files`);
|
||||||
|
}
|
||||||
|
|
||||||
|
generateContent();
|
||||||
Reference in New Issue
Block a user