Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
d693008
change site domain and name in config
Feretj Sep 22, 2025
68980f7
handle error when fetching author
Feretj Sep 22, 2025
584afb1
change default theme to light
Feretj Sep 22, 2025
f2a8ede
change favicon
Feretj Sep 22, 2025
b2ebff5
remake home page
Feretj Sep 22, 2025
5b76869
change navbar and footer
Feretj Sep 22, 2025
ba9d380
add docker config
Feretj Sep 22, 2025
9d15a0c
refactor: remove WORDPRESS_HOSTNAME env variable
Feretj Oct 8, 2025
c39a66e
refactor: remove static generation of post pages
Feretj Oct 8, 2025
4376aeb
refactor: add prefix "NEXT" to env variables
Feretj Oct 8, 2025
c05142a
feat: fetch 100 authors, categories, tags and pages
Feretj Oct 21, 2025
ee7094a
refactor: migrate to docker entrypoint with next js build
Feretj Oct 22, 2025
9d202ea
fix: comment out post in sitemap
Feretj Oct 22, 2025
5b3bf41
feat: create NEXT_PUBLIC_WORDPRESS_URL env variable
Feretj Oct 22, 2025
2f82cc3
fix: remove placeholder image if none for preview
ananastii Oct 28, 2025
67b1f1a
feat: add .env to .gitignore
Feretj Oct 29, 2025
d8d6379
refactor: change production Dockerfile to use standalone Next.js output
Feretj Oct 29, 2025
af78548
feat: add Dockerfile.dev
Feretj Oct 29, 2025
1108757
feat: add compose.yml for development container
Feretj Oct 29, 2025
62858e7
feat: add Makefile
Feretj Oct 29, 2025
cbdb1fb
docs: add make usage information
Feretj Oct 29, 2025
b20b967
Merge pull request #2 from sysblok/feat/config
Feretj Oct 29, 2025
d6ffd49
Merge pull request #3 from sysblok/fix/no-image
Feretj Oct 30, 2025
4880c13
fix: remove production arg when intalling packages in docker
Feretj Oct 30, 2025
7f25a80
feat: create higher order function to get data from all fetch pages
Feretj Nov 1, 2025
69c184b
fix: add comma array format in querystring stringify
Feretj Nov 1, 2025
8262bcc
refactor: rewrite getAllTags with createGetAll
Feretj Nov 1, 2025
4747744
refactor: add TagFields and hide empty
Feretj Nov 1, 2025
ad86c0a
refactor: add categoryFields and rewrite getAllCategories
Feretj Nov 1, 2025
c96eb34
feat: add pageFields and authorFields
Feretj Nov 1, 2025
89e6384
refactor: add postFields
Feretj Nov 1, 2025
1dfb571
refactor: add mediaFields
Feretj Nov 1, 2025
b9f304a
refactor: create PostQuery interface and update posts api functions
Feretj Nov 1, 2025
f2864d4
feat: add SSG to posts pages
Feretj Nov 1, 2025
0ab38aa
feat: create type for query string in api requests
Feretj Nov 2, 2025
2ac8265
refactor: leave one wordpress url env variable
Feretj Nov 2, 2025
6497843
feat: return posts links to sitemap and add url env variable
Feretj Nov 2, 2025
b9e96d3
feat: create card post type for lists of posts
Feretj Nov 2, 2025
460e0ea
feat: extract text from excerpt
Feretj Nov 2, 2025
53b9563
refactor: declare chacheTag type and add to all api methods
Feretj Nov 2, 2025
edc7a92
feat: add media and manual revalidation
Feretj Nov 2, 2025
fec6a49
feat: add pages revalidation with slug
Feretj Nov 2, 2025
f0629be
chore: add slug to revalidate log
Feretj Nov 2, 2025
8d8d519
feat: extend fetch cache to one day
Feretj Nov 2, 2025
39dbbd5
chore: change default app domain
Feretj Nov 4, 2025
c289761
Merge pull request #4 from sysblok/feat/wordpress-api
Feretj Nov 4, 2025
be9c7ad
feat: add coauthors
Tabarzin Nov 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
WORDPRESS_URL="https://wordpress.com"
WORDPRESS_HOSTNAME="wordpress.com"
NEXT_PUBLIC_WORDPRESS_URL="https://sysblok.ru"
NEXT_PUBLIC_URL="https://next.sysblok.team"

# If using the revalidate plugin
# You can generate by running `openssl rand -base64 32` in the terminal
WORDPRESS_WEBHOOK_SECRET="your-secret-key-here"
NEXT_WORDPRESS_WEBHOOK_SECRET="your-secret-key-here"
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
# env files
.env*.local
.env

# vercel
.vercel
Expand Down
5 changes: 2 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,8 @@ This is a headless WordPress starter using Next.js 15 App Router with TypeScript

## Environment Variables
Required environment variables (see `.env.example`):
- `WORDPRESS_URL` - Full URL of WordPress site
- `WORDPRESS_HOSTNAME` - Domain for image optimization
- `WORDPRESS_WEBHOOK_SECRET` - Secret for webhook validation
- `NEXT_PUBLIC_WORDPRESS_URL` - Full URL of WordPress site
- `NEXT_WORDPRESS_WEBHOOK_SECRET` - Secret for webhook validation

## Key Dependencies
- Next.js 15.3.3 with React 19.1.0
Expand Down
75 changes: 75 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# syntax=docker.io/docker/dockerfile:1
# ============================================
# Base Stage: Use a Lightweight Node.js Image
# ============================================

# Use an official Node.js Alpine image
ARG NODE_VERSION=22.14.0-alpine
FROM node:${NODE_VERSION} AS base

# Set the working directory
WORKDIR /app

# ============================================
# Stage 2: Install dependencies
# ============================================
FROM base AS deps

# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat

# Copy package related files
COPY package.json pnpm-lock.yaml ./

# Enable pnpm
RUN corepack enable pnpm

# Install dependencies
RUN pnpm i --frozen-lockfile

# ============================================
# Stage 3: Build Next.js app
# ============================================
FROM base AS builder

# Copy node modules from dependencies
COPY --from=deps /app/node_modules ./node_modules

# Copy source code into the container
COPY . .

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Following line disables telemetry.
ENV NEXT_TELEMETRY_DISABLED=1

# Build the application
RUN npm run build
Comment on lines +33 to +47
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent package manager usage.

The deps stage installs dependencies with pnpm (line 28), but the builder stage uses npm run build (line 47). For consistency and to ensure the lockfile is respected, consider using pnpm throughout.

Apply this diff to use pnpm for the build:

-# Build the application 
-RUN npm run build
+# Build the application
+RUN pnpm build
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
FROM base AS builder
# Copy node modules from dependencies
COPY --from=deps /app/node_modules ./node_modules
# Copy source code into the container
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Following line disables telemetry.
ENV NEXT_TELEMETRY_DISABLED=1
# Build the application
RUN npm run build
FROM base AS builder
# Copy node modules from dependencies
COPY --from=deps /app/node_modules ./node_modules
# Copy source code into the container
COPY . .
# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Following line disables telemetry.
ENV NEXT_TELEMETRY_DISABLED=1
# Build the application
RUN pnpm build
🤖 Prompt for AI Agents
In Dockerfile around lines 33 to 47, the builder stage uses npm to run the build
despite dependencies being installed with pnpm in the deps stage; replace the
npm invocation with pnpm (e.g., change RUN npm run build to RUN pnpm run build)
and ensure pnpm is available in the builder image (either install pnpm or enable
Corepack before running the build) so the lockfile and package manager remain
consistent.


# ============================================
# Stage 4: Create Production Image
# ============================================
FROM base AS runner

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001

# Copy standalone build files
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

# Set the port and hostname for the Next.js standalone server
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

# Use nextjs user for security best practices
USER nextjs

# Expose port 3000
EXPOSE 3000

CMD ["node", "server.js"]
32 changes: 32 additions & 0 deletions Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# syntax=docker.io/docker/dockerfile:1
# Use an official Node.js Alpine image
ARG NODE_VERSION=22.14.0-alpine
FROM node:${NODE_VERSION} AS base

# Set the working directory
WORKDIR /app

# Copy package related files
COPY package.json pnpm-lock.yaml ./

# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat

# Enable pnpm
RUN corepack enable pnpm

# Install dependencies
RUN pnpm i --frozen-lockfile

# Copy source code into the container
COPY . .

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Following line disables telemetry.
ENV NEXT_TELEMETRY_DISABLED=1

# Expose port 3000
EXPOSE 3000

CMD ["npm", "run", "dev"]
90 changes: 90 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Variables
IMAGE_NAME = wp-nextjs
IMAGE_TAG = latest
CONTAINER_NAME = wp-nextjs-container
HOST_PORT = 3000
CONTAINER_PORT = 3000
DOCKERFILE = Dockerfile
NODE_VERSION = 22.14.0-alpine
NODE_ENV=production

# Default target
.PHONY: help
help:
@echo "Available commands:"
@echo " make build - Build the production Docker image"
@echo " make build-dev - Build the development Docker image and create container"
@echo " make run - Run the production Docker container"
@echo " make run-dev - Build and run the development Docker container"
@echo " make start-dev - Start the development Docker container"
@echo " make build-run - Build and run the production Docker container"
@echo " make stop - Stop the production Docker container"
@echo " make stop-dev - Stop the development Docker container"
@echo " make down-dev - Stop and remove the development Docker container"
@echo " make restart - Restart the production Docker container"
@echo " make restart-dev - Restart the development Docker container"
@echo " make logs - Show production container logs"
@echo " make clean - Remove production Docker image and container"

# Build the production Docker image
build:
docker build \
--build-arg NODE_VERSION=$(NODE_VERSION) \
-f $(DOCKERFILE) -t $(IMAGE_NAME):$(IMAGE_TAG) .

# Build the development Docker image and create container
build-dev:
docker compose build
docker compose create

# Run the production Docker container
run:
@docker rm -f $(CONTAINER_NAME) 2>/dev/null || true
docker run --name $(CONTAINER_NAME) -p $(HOST_PORT):$(CONTAINER_PORT) $(IMAGE_NAME):$(IMAGE_TAG)

# Build and run the development Docker container
run-dev:
docker compose up --build

# Start the development Docker container
start-dev:
docker compose start

# Build and run the production Docker container in one step
build-run: build run

# Stop the production Docker container
stop:
docker stop $(CONTAINER_NAME)
Comment on lines +57 to +58
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make stop idempotent so restart succeeds.

Right now restart depends on stop, but docker stop $(CONTAINER_NAME) exits 1 when the container is absent, causing make restart to abort before run executes. Add a harmless fallback so the target succeeds even if the container is already down.

 stop:
-	docker stop $(CONTAINER_NAME)
+	docker stop $(CONTAINER_NAME) || true
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
stop:
docker stop $(CONTAINER_NAME)
stop:
docker stop $(CONTAINER_NAME) || true
🤖 Prompt for AI Agents
In Makefile around lines 57 to 58, the stop target fails when the container is
absent which makes restart abort; make stop idempotent by making the docker stop
command tolerate a missing container (for example append "|| true" or redirect
errors and "|| true") so the target returns success even if the container is
already stopped, preserving current behavior when the container exists.


# Stop the development Docker container
stop-dev:
docker compose stop

# Clean the development Docker container
down-dev:
docker compose down

# Restart the production Docker container
restart: stop run

# Restart the development Docker container
restart-dev:
docker compose restart

# Show logs from the production Docker container
logs:
docker logs -f $(CONTAINER_NAME)

# Clean up by removing production Docker image and container
clean:
-docker rm -f $(CONTAINER_NAME)
-docker rmi $(IMAGE_NAME):$(IMAGE_TAG)

# Clean up by removing production Docker container
clean-container:
docker rm -f $(CONTAINER_NAME)

# Clean up by removing production Docker image and container
clean-image:
docker rmi $(IMAGE_NAME):$(IMAGE_TAG)
36 changes: 23 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
# Next.js Starter for WordPress Headless CMS
# Next.js frontend for sysblok

> [Watch the Demo Video](https://www.youtube.com/watch?v=JZc1-BcOvYw)
>
> [Need a headless theme? Use 761](https://github.com/9d8dev/761)
This is Next.js application that fetches data from a WordPress site using the WordPress REST API. It includes functions for fetching posts, categories, tags, authors, and featured media from a WordPress site and rendering them in a Next.js application.

![CleanShot 2025-01-07 at 23 18 41@2x](https://github.com/user-attachments/assets/8b268c36-eb0d-459f-b9f1-b5f129bd29bc)
`sysblok-next` is built with [Next.js 15](https://nextjs.org/docs), [React](https://react.dev/), [Typescript](https://www.typescriptlang.org/docs/), [Tailwind](https://tailwindcss.com/), [shadcn/ui](https://ui.shadcn.com/docs), and [brijr/craft](https://github.com/brijr/craft). It pairs nicely with [brijr/components](https://components.bridger.to/) for a rapid development experience. Built by Cameron and Bridger at [9d8](https://9d8.dev).

[![Deploy with Vercel](https://vercel.com/button)](<https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2F9d8dev%2Fnext-wp&env=WORDPRESS_URL,WORDPRESS_HOSTNAME,WORDPRESS_WEBHOOK_SECRET&envDescription=Add%20WordPress%20URL%20with%20Rest%20API%20enabled%20(ie.%20https%3A%2F%2Fwp.example.com)%2C%20the%20hostname%20for%20Image%20rendering%20in%20Next%20JS%20(ie.%20wp.example.com)%2C%20and%20a%20secret%20key%20for%20secure%20revalidation&project-name=next-wp&repository-name=next-wp&demo-title=Next%20JS%20and%20WordPress%20Starter&demo-url=https%3A%2F%2Fwp.9d8.dev>)
## Make usage

This is a starter template for building a Next.js application that fetches data from a WordPress site using the WordPress REST API. The template includes functions for fetching posts, categories, tags, authors, and featured media from a WordPress site and rendering them in a Next.js application.
* `make build` - Build the production Docker image
* `make build-dev` - Build the development Docker image and create container
* `make run` - Run the production Docker container
* `make run-dev` - Build and run the development Docker container
* `make start-dev` - Start the development Docker container
* `make build-run` - Build and run the production Docker container
* `make stop` - Stop the production Docker container
* `make stop-dev` - Stop the development Docker container
* `make down-dev` - Stop and remove the development Docker container
* `make restart` - Restart the production Docker container
* `make restart-dev` - Restart the development Docker container
* `make logs` - Show production container logs
* `make clean` - Remove production Docker image and container

To start dev server without docker - `npm run dev`

`next-wp` is built with [Next.js 15](https://nextjs.org/docs), [React](https://react.dev/), [Typescript](https://www.typescriptlang.org/docs/), [Tailwind](https://tailwindcss.com/), [shadcn/ui](https://ui.shadcn.com/docs), and [brijr/craft](https://github.com/brijr/craft). It pairs nicely with [brijr/components](https://components.bridger.to/) for a rapid development experience. Built by Cameron and Bridger at [9d8](https://9d8.dev).

## Table of Contents

Expand Down Expand Up @@ -67,8 +78,7 @@ The following environment variables are required in your `.env.local` file:

```bash
WORDPRESS_URL="https://wordpress.com"
WORDPRESS_HOSTNAME="wordpress.com"
WORDPRESS_WEBHOOK_SECRET="your-secret-key-here"
NEXT_WORDPRESS_WEBHOOK_SECRET="your-secret-key-here"
```

You can find the example of `.env.local` file in the `.env.example` file (and in Vercel).
Expand Down Expand Up @@ -132,7 +142,7 @@ const defaultFetchOptions = {

#### Media

- `getFeaturedMediaById(id: number)`: Retrieves featured media (images) with size information.
- `getMediaById(id: number)`: Retrieves featured media (images) with size information.

### Error Handling

Expand Down Expand Up @@ -393,7 +403,7 @@ The `components/posts/post-card.tsx` file contains the `PostCard` component, whi

### Functionality

1. The component fetches the featured media, author, and category associated with the post using the `getFeaturedMediaById`, `getAuthorById`, and `getCategoryById` functions from `lib/wordpress.ts`.
1. The component fetches the featured media, author, and category associated with the post using the `getMediaById`, `getAuthorById`, and `getCategoryById` functions from `lib/wordpress.ts`.

2. It formats the post date using the `toLocaleDateString` method with the specified options.

Expand Down Expand Up @@ -586,7 +596,7 @@ This granular system ensures that when content changes, only the relevant cached

2. **Configure Next.js:**

- Add `WORDPRESS_WEBHOOK_SECRET` to your environment variables (same secret as in WordPress plugin)
- Add `NEXT_WORDPRESS_WEBHOOK_SECRET` to your environment variables (same secret as in WordPress plugin)
- The webhook endpoint at `/api/revalidate` is already set up
- No additional configuration needed

Expand Down
26 changes: 16 additions & 10 deletions app/api/revalidate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ export async function POST(request: NextRequest) {
const requestBody = await request.json();
const secret = request.headers.get("x-webhook-secret");

if (secret !== process.env.WORDPRESS_WEBHOOK_SECRET) {
if (secret !== process.env.NEXT_WORDPRESS_WEBHOOK_SECRET) {
console.error("Invalid webhook secret");
return NextResponse.json(
{ message: "Invalid webhook secret" },
{ status: 401 }
);
}

const { contentType, contentId } = requestBody;
const { contentType, contentId, contentSlug } = requestBody;

if (!contentType) {
return NextResponse.json(
Expand All @@ -35,19 +35,19 @@ export async function POST(request: NextRequest) {
console.log(
`Revalidating content: ${contentType}${
contentId ? ` (ID: ${contentId})` : ""
}${
contentSlug ? ` (slug: ${contentSlug})` : ""
}`
);

// Revalidate specific content type tags
revalidateTag("wordpress");

if (contentType === "post") {
revalidateTag("posts");
if (contentId) {
revalidateTag(`post-${contentId}`);
}
// Clear all post pages when any post changes
revalidateTag("posts-page-1");
if (contentId) revalidateTag(`post-${contentId}`);
if (contentSlug) revalidateTag(`post-${contentSlug}`);
} else if (contentType === "page") {
revalidateTag("pages");
if (contentSlug) revalidateTag(`page-${contentSlug}`);
if (contentId) revalidateTag(`page-${contentId}`);
} else if (contentType === "category") {
revalidateTag("categories");
if (contentId) {
Expand All @@ -66,6 +66,12 @@ export async function POST(request: NextRequest) {
revalidateTag(`posts-author-${contentId}`);
revalidateTag(`author-${contentId}`);
}
} else if (contentType === "media") {
if (contentId) revalidateTag(`media-${contentId}`);
else revalidateTag(`media`);
} else if (contentType !== "menu") {
// revalidate all wordpress requests
revalidateTag("wordpress");
}

// Also revalidate the entire layout for safety
Expand Down
Binary file modified app/favicon.ico
Binary file not shown.
Loading