Skip to content

Conversation

@Tabarzin
Copy link

@Tabarzin Tabarzin commented Nov 7, 2025

It seems working
image

Summary by CodeRabbit

  • New Features

    • Added Docker support for streamlined development and production deployments.
    • Introduced Makefile commands for simplified build and container management workflows.
    • Redesigned homepage to display posts in a grid layout from the news category.
  • Style

    • Updated site branding and metadata.
    • Refined navigation by removing the "Get Started" button and optimizing layout spacing.
    • Conditional blog menu rendering in navigation when content is available.
  • Documentation

    • Updated README with Docker and Makefile usage instructions.
    • Added comprehensive deployment guidance for local and production environments.

Feretj and others added 30 commits September 22, 2025 17:13
fix: remove placeholder image if none for preview
@vercel
Copy link

vercel bot commented Nov 7, 2025

@Tabarzin is attempting to deploy a commit to the 9d8 Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link

coderabbitai bot commented Nov 7, 2025

Walkthrough

Comprehensive project restructuring featuring environment variable renaming (WORDPRESS_URL → NEXT_PUBLIC_WORDPRESS_URL, WORDPRESS_WEBHOOK_SECRET → NEXT_WORDPRESS_WEBHOOK_SECRET), Docker containerization infrastructure, major data layer refactoring with typed queries and cache tagging, simplified homepage rendering, and enhanced webhook revalidation with slug-aware cache invalidation.

Changes

Cohort / File(s) Summary
Environment & Config Updates
.env.example, site.config.ts, next.config.ts, menu.config.ts
Renamed WordPress env vars to NEXT_PUBLIC_* format; updated siteConfig to derive domain from NEXT_PUBLIC_URL and rebrand content; hardcoded remote image host to sysblok.ru; restructured navigation menus (removed external links, added posts/pages/authors routes).
Docker & Container Infrastructure
Dockerfile, Dockerfile.dev, Makefile, compose.yml, .dockerignore
Added multi-stage production Dockerfile (Alpine-based, pnpm, non-root nextjs user), dev Dockerfile with hot-reload, Makefile with build/run/clean targets for both environments, docker-compose config with volume mounts, and dockerignore patterns.
Documentation
CLAUDE.md, README.md, plugin/README.md, plugin/next-revalidate/README.txt
Updated env var references from WORDPRESS_* to NEXT_* conventions; rewrote project description/branding; added Makefile usage section; updated webhook secret guidance throughout.
.gitignore Renamed env comment from "local env files" to "env files"; added .env pattern alongside existing .env*.local.
Type System & Query Definitions
lib/wordpress.d.ts
Renamed FeaturedMediaMedia; expanded Post interface with type, author_meta, embedded links, and featured image fields; introduced pagination headers; added generic query types (PostQuery, PageQuery, MediaQuery, etc.); introduced CacheTag union type for granular cache invalidation.
Data Layer Refactor
lib/wordpress.ts
Migrated to NEXT_PUBLIC_WORDPRESS_URL; added generic createGetAll<T> factory for pagination; refactored all fetchers (posts, pages, categories, tags, authors, media) to use typed field-sets and cache tags; introduced CardPost type for optimized card rendering; added getPostData(slug) composite fetcher; added getCoAuthorsByPost(postId) for co-author resolution; updated wordpressFetch/wordpressFetchWithPagination to accept cacheTags array.
Utility Helpers
lib/utils.ts
Added stripHtmlTags(htmlString) and extractExcerptText(excerpt) for excerpt processing in card views.
Homepage & Posts Pages
app/page.tsx, app/pages/page.tsx, app/pages/[slug]/page.tsx, app/posts/page.tsx, app/posts/[slug]/page.tsx, app/sitemap.ts
Converted Home to async function; added "news" category filtering and paginated post grid; simplified multi-card layout to simple prose header; updated data fetching to use new field-limited queries; refactored post detail page to use composite getPostData() and getMediaById(); added error handling for missing posts; updated slug generation to use new API patterns.
Revalidation & Webhook
app/api/revalidate/route.ts, plugin/next-revalidate/next-revalidate.php
Updated env var from WORDPRESS_WEBHOOK_SECRET to NEXT_WORDPRESS_WEBHOOK_SECRET; added contentSlug extraction from webhook payload; introduced slug-aware cache tags (post-{id}, post-{slug}, page-{slug}, etc.); expanded post/page/media revalidation logic; refined hook priorities and post context handling in plugin; plugin now passes post_name (slug) to revalidation requests.
Components
components/posts/post-card.tsx, components/nav/mobile-nav.tsx
Updated PostCard to accept CardPost type; changed media resolution to prefer embedded wp:featuredmedia with fallback; updated excerpt to use extractExcerptText with 24-word truncation; removed author retrieval; added category fallback to embedded wp:term; made Blog Menu rendering conditional on contentMenu entries.

Sequence Diagram(s)

sequenceDiagram
    participant WordPress as WordPress REST API
    participant Next as Next.js App
    participant Cache as Next.js Data Cache
    participant Webhook as WP Plugin Webhook
    participant Revalidate as /api/revalidate Route

    Note over Webhook: Post published/updated with slug
    Webhook->>Revalidate: Send contentId, contentType, contentSlug
    Revalidate->>Revalidate: Extract & validate payload
    activate Revalidate
        Note over Revalidate: Generate cache tags:<br/>post-{id}, post-{slug}<br/>posts, wordpress (if default)
        Revalidate->>Cache: revalidateTag(tag) per type
        Revalidate->>Cache: revalidatePath("/layout")
    deactivate Revalidate
    
    participant User as User Browser
    User->>Next: Request page/post
    activate Next
        alt Cache hit
            Next->>Cache: Check cache tags
            Cache-->>Next: Return cached data
        else Cache miss/revalidated
            Next->>WordPress: Fetch with _fields=[slug, title...]
            WordPress-->>Next: Return typed fields
            Next->>Cache: Store with tags
        end
    deactivate Next
    Next-->>User: Render page
Loading
sequenceDiagram
    participant Client as Client Browser
    participant App as app/page.tsx (async)
    participant WP as lib/wordpress.ts
    participant Cache as fetch cache + revalidate

    Client->>App: Request /
    activate App
        App->>WP: getPostsPaginated(1, 30, {categories: !news_id})
        activate WP
            WP->>WP: Build query with _fields: postCardFields
            WP->>Cache: wordpressFetchWithPagination(..., cacheTags)
            Cache->>Cache: Fetch with 1-day revalidation
            WP-->>App: WordPressResponse<CardPost[]>
        deactivate WP
        App->>App: Render Container with Prose + PostCard grid
        alt Posts exist
            App->>Client: Render 3-col grid of CardPost
        else No posts
            App->>Client: "No posts found" message
        end
    deactivate App
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Areas requiring extra attention:

  • lib/wordpress.ts and lib/wordpress.d.ts: Major architectural shift with new generic pagination patterns, cache tagging strategy, field-set definitions, and query type system. Verify consistency across all endpoints, correct cache tag generation, and backward compatibility gaps.
  • app/api/revalidate/route.ts: Complex slug-aware cache invalidation logic with per-content-type branching. Validate revalidation tag correctness for posts/pages/media and fallback behavior.
  • plugin/next-revalidate/next-revalidate.php: Hook priority changes, post context resolution, and slug payload inclusion. Ensure post_name is correctly passed through all revalidation triggers and no edge cases (trash/delete) are missed.
  • components/posts/post-card.tsx: Updated from Post to CardPost type with embedded media/category resolution; verify embedded data extraction works and fallback logic is sound.
  • Docker/Makefile/compose.yml: Validate Alpine image compatibility, pnpm setup, volume mounts (especially node_modules preservation), and development watch patterns.
  • Type migrations across files: Multiple files now depend on new query types (WordPressQuery, PostQuery, etc.). Verify call sites have been migrated and no legacy patterns remain.

Possibly related PRs

Suggested reviewers

  • brijr
  • youngbloodcyb

Poem

🐰 A rabbit hops through Docker files,

With cache tags sparkling, typed for miles,

The webhook slugs now dance and play,

Revalidation's smart today!

From WordPress whispers to Next.js dreams—

The refactor flows in pristine streams. 🌿✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title 'Feat/add coauthors 2' refers to a real aspect of the changeset (coauthor support is added via getCoAuthorsByPost and related functions), but significantly undersells the main changes. The PR contains extensive infrastructure changes (Docker setup, Makefile, compose.yml), major API refactoring (generic fetch helpers, cache tagging), environment variable renames, configuration updates, and UI/branding overhaul that dwarf the coauthors feature. Revise the title to reflect the primary changes, such as 'Refactor WordPress data fetching with cache tagging and Docker support' or similar, or clarify if coauthors is truly the focus given the scope of other modifications.
Docstring Coverage ⚠️ Warning Docstring coverage is 12.28% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
app/posts/[slug]/page.tsx (1)

82-112: Primary author disappears when no co-authors

getPostData only returns co-author slugs, so for a normal single-author post the authors array comes back empty. The new rendering path therefore drops the byline entirely, which is a regression from the previous behavior. Please fall back to the primary author (via getAuthorById(post.author)) when authors is empty, and reuse that list for the UI. This keeps co-author support while preserving the existing single-author output.

-  const { post, featuredMedia, category, authors } = postData;
+  const { post, featuredMedia, category, authors } = postData;
+  const primaryAuthor =
+    (!authors || authors.length === 0) && post.author
+      ? await getAuthorById(post.author)
+      : null;
+  const displayAuthors =
+    authors && authors.length > 0
+      ? authors
+      : primaryAuthor
+        ? [primaryAuthor]
+        : [];
@@
-              {authors?.length > 0 && (
+              {displayAuthors.length > 0 && (
                 <>
                   {" by "}
-                  {authors.map((a: any, i: number) => (
+                  {displayAuthors.map((a: any, i: number) => (
                     <span key={a.id}>
                       <a href={`/posts/?author=${a.id}`}>{a.name}</a>
-                      {i < authors.length - 1 ? ", " : ""}
+                      {i < displayAuthors.length - 1 ? ", " : ""}
                     </span>
                   ))}
                 </>
               )}
components/posts/post-card.tsx (1)

23-59: Avoid indexing into empty term arrays.

On Line 24 the code reads post._embedded?.["wp:term"][0][0].taxonomy without verifying that the inner array has an element. If the API returns an empty term list, term[0] is undefined and React will crash rendering this card. Please guard against empty arrays before dereferencing.

-  const category = post._embedded?.["wp:term"][0] && post._embedded?.["wp:term"][0][0].taxonomy === "category" ?
-    post._embedded?.["wp:term"][0][0]
-    : post.categories?.[0]
-    ? await getCategoryById(post.categories[0])
-    : null;
+  const primaryTerms = post._embedded?.["wp:term"]?.[0];
+  const embeddedCategory =
+    Array.isArray(primaryTerms) && primaryTerms[0]?.taxonomy === "category"
+      ? primaryTerms[0]
+      : null;
+  const category =
+    embeddedCategory ??
+    (post.categories?.[0] ? await getCategoryById(post.categories[0]) : null);
🧹 Nitpick comments (3)
.env.example (1)

1-6: Remove quotes from environment variable values.

In .env files, quotes around URLs are typically unnecessary and can cause issues as they may be included in the actual value. The dotenv-linter static analysis tool has flagged this.

Apply this diff to remove the quotes:

-NEXT_PUBLIC_WORDPRESS_URL="https://sysblok.ru"
-NEXT_PUBLIC_URL="https://next.sysblok.team"
+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
-NEXT_WORDPRESS_WEBHOOK_SECRET="your-secret-key-here"
+NEXT_WORDPRESS_WEBHOOK_SECRET=your-secret-key-here
next.config.ts (1)

1-21: Remove commented code.

This commented-out configuration should be removed. Version control preserves the history, so there's no need to keep it in the file.

Apply this diff:

-// import type { NextConfig } from "next";
-
-// const nextConfig: NextConfig = {
-//   images: {
-//     remotePatterns: [
-//       new URL(`${process.env.NEXT_PUBLIC_WORDPRESS_URL}/wp-content/**`),
-//     ],
-//   },
-//   async redirects() {
-//     return [
-//       {
-//         source: "/admin",
-//         destination: `${process.env.NEXT_PUBLIC_WORDPRESS_URL}/wp-admin`,
-//         permanent: true,
-//       },
-//     ];
-//   },
-//   output: "standalone",
-// };
-
-// export default nextConfig;
-
 import type { NextConfig } from "next";
Dockerfile.dev (1)

1-32: Consider using pnpm consistently for both install and run commands.

The Dockerfile uses pnpm for dependency installation (line 19) but npm for running the dev server (line 32). While this works because both package managers can read the same package.json scripts, using pnpm consistently would be clearer.

Apply this diff:

-CMD ["npm", "run", "dev"]
+CMD ["pnpm", "run", "dev"]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3abffe5 and be9c7ad.

⛔ Files ignored due to path filters (2)
  • app/favicon.ico is excluded by !**/*.ico
  • plugin/next-revalidate.zip is excluded by !**/*.zip
📒 Files selected for processing (28)
  • .env.example (1 hunks)
  • .gitignore (1 hunks)
  • CLAUDE.md (1 hunks)
  • Dockerfile (1 hunks)
  • Dockerfile.dev (1 hunks)
  • Makefile (1 hunks)
  • README.md (5 hunks)
  • app/api/revalidate/route.ts (3 hunks)
  • app/layout.tsx (5 hunks)
  • app/page.tsx (1 hunks)
  • app/pages/[slug]/page.tsx (1 hunks)
  • app/pages/page.tsx (1 hunks)
  • app/posts/[slug]/page.tsx (4 hunks)
  • app/posts/page.tsx (1 hunks)
  • app/sitemap.ts (1 hunks)
  • components/nav/mobile-nav.tsx (1 hunks)
  • components/posts/post-card.tsx (2 hunks)
  • compose.yml (1 hunks)
  • dockerignore (1 hunks)
  • lib/utils.ts (1 hunks)
  • lib/wordpress.d.ts (6 hunks)
  • lib/wordpress.ts (9 hunks)
  • menu.config.ts (1 hunks)
  • next.config.ts (1 hunks)
  • plugin/README.md (1 hunks)
  • plugin/next-revalidate/README.txt (1 hunks)
  • plugin/next-revalidate/next-revalidate.php (6 hunks)
  • site.config.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: 9d8dev/next-wp PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-06-26T17:28:47.375Z
Learning: When working with a headless WordPress starter using Next.js 15 App Router and TypeScript, centralize all WordPress API interactions in a single module (e.g., lib/wordpress.ts) to maintain a clean data layer.
Learnt from: CR
Repo: 9d8dev/next-wp PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-06-26T17:28:47.375Z
Learning: Set up a revalidation system where a WordPress plugin sends webhooks to a Next.js endpoint, which validates the webhook secret and calls revalidateTag(), with a default cache duration (e.g., 1 hour).
📚 Learning: 2025-06-26T17:28:47.375Z
Learnt from: CR
Repo: 9d8dev/next-wp PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-06-26T17:28:47.375Z
Learning: Store required environment variables (such as WORDPRESS_URL, WORDPRESS_HOSTNAME, WORDPRESS_WEBHOOK_SECRET) in a .env.example file to document configuration requirements.

Applied to files:

  • CLAUDE.md
  • plugin/README.md
  • .env.example
  • README.md
📚 Learning: 2025-06-26T17:28:47.375Z
Learnt from: CR
Repo: 9d8dev/next-wp PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-06-26T17:28:47.375Z
Learning: Set up a revalidation system where a WordPress plugin sends webhooks to a Next.js endpoint, which validates the webhook secret and calls revalidateTag(), with a default cache duration (e.g., 1 hour).

Applied to files:

  • CLAUDE.md
  • plugin/README.md
  • plugin/next-revalidate/README.txt
  • .env.example
  • README.md
  • plugin/next-revalidate/next-revalidate.php
  • app/api/revalidate/route.ts
  • lib/wordpress.ts
📚 Learning: 2025-06-26T17:28:47.375Z
Learnt from: CR
Repo: 9d8dev/next-wp PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-06-26T17:28:47.375Z
Learning: Leverage Next.js cache tags in data-fetching functions to enable granular and efficient cache revalidation.

Applied to files:

  • app/posts/page.tsx
  • lib/wordpress.ts
📚 Learning: 2025-06-26T17:28:47.375Z
Learnt from: CR
Repo: 9d8dev/next-wp PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-06-26T17:28:47.375Z
Learning: Use parallel data fetching with Promise.all() in Server Components to optimize performance.

Applied to files:

  • app/posts/page.tsx
📚 Learning: 2025-06-26T17:28:47.375Z
Learnt from: CR
Repo: 9d8dev/next-wp PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-06-26T17:28:47.375Z
Learning: When working with a headless WordPress starter using Next.js 15 App Router and TypeScript, centralize all WordPress API interactions in a single module (e.g., lib/wordpress.ts) to maintain a clean data layer.

Applied to files:

  • next.config.ts
  • app/page.tsx
  • README.md
  • app/api/revalidate/route.ts
  • lib/wordpress.d.ts
  • app/posts/[slug]/page.tsx
  • lib/wordpress.ts
📚 Learning: 2025-06-26T17:28:47.375Z
Learnt from: CR
Repo: 9d8dev/next-wp PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-06-26T17:28:47.375Z
Learning: Define all TypeScript interfaces for WordPress entities (Post, Page, Category, Tag, Author, Media) in a dedicated type definition file (e.g., lib/wordpress.d.ts) to ensure strict typing and maintainability.

Applied to files:

  • README.md
  • components/posts/post-card.tsx
  • lib/wordpress.d.ts
  • app/posts/[slug]/page.tsx
  • lib/wordpress.ts
🧬 Code graph analysis (11)
app/pages/page.tsx (1)
lib/wordpress.ts (1)
  • getAllPages (396-400)
app/layout.tsx (3)
components/craft.tsx (1)
  • Container (227-231)
site.config.ts (1)
  • siteConfig (13-17)
menu.config.ts (1)
  • contentMenu (12-13)
app/posts/page.tsx (1)
lib/wordpress.ts (1)
  • getPostsPaginated (197-229)
app/sitemap.ts (1)
lib/wordpress.ts (1)
  • getAllPosts (231-237)
app/pages/[slug]/page.tsx (1)
lib/wordpress.ts (1)
  • getAllPages (396-400)
components/nav/mobile-nav.tsx (1)
menu.config.ts (1)
  • contentMenu (12-13)
app/page.tsx (3)
lib/wordpress.ts (2)
  • getCategoryBySlug (319-325)
  • getPostsPaginated (197-229)
components/craft.tsx (2)
  • Container (227-231)
  • Prose (253-266)
components/posts/post-card.tsx (1)
  • PostCard (12-71)
components/posts/post-card.tsx (2)
lib/wordpress.ts (2)
  • CardPost (181-194)
  • getMediaById (513-521)
lib/utils.ts (1)
  • extractExcerptText (16-22)
lib/wordpress.d.ts (2)
app/posts/[slug]/page.tsx (1)
  • Page (69-143)
app/posts/page.tsx (1)
  • Page (35-164)
app/posts/[slug]/page.tsx (1)
lib/wordpress.ts (2)
  • getAllPostSlugs (563-563)
  • getPostData (635-660)
lib/wordpress.ts (3)
lib/wordpress.d.ts (9)
  • WordPressQuery (325-332)
  • CacheTag (334-349)
  • WordPressResponse (252-255)
  • Post (55-92)
  • Category (119-122)
  • Tag (124-126)
  • Page (94-106)
  • Author (128-137)
  • Media (41-52)
app/posts/[slug]/page.tsx (1)
  • Page (69-143)
app/posts/page.tsx (1)
  • Page (35-164)
🪛 ast-grep (0.39.7)
components/posts/post-card.tsx

[warning] 49-49: Usage of dangerouslySetInnerHTML detected. This bypasses React's built-in XSS protection. Always sanitize HTML content using libraries like DOMPurify before injecting it into the DOM to prevent XSS attacks.
Context: dangerouslySetInnerHTML
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation [REFERENCES]
- https://reactjs.org/docs/dom-elements.html#dangerouslysetinnerhtml
- https://cwe.mitre.org/data/definitions/79.html

(react-unsafe-html-injection)

🪛 Biome (2.1.2)
components/posts/post-card.tsx

[error] 50-50: Avoid passing content using the dangerouslySetInnerHTML prop.

Setting content using code can expose users to cross-site scripting (XSS) attacks

(lint/security/noDangerouslySetInnerHtml)

🪛 checkmake (0.2.2)
Makefile

[warning] 69-69: Target "restart" should be declared PHONY.

(phonydeclared)


[warning] 13-13: Target body for "help" exceeds allowed length of 5 (14).

(maxbodylength)


[warning] 12-12: Missing required phony target "all"

(minphony)


[warning] 12-12: Missing required phony target "clean"

(minphony)


[warning] 12-12: Missing required phony target "test"

(minphony)

🪛 dotenv-linter (4.0.0)
.env.example

[warning] 1-1: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 2-2: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)


[warning] 2-2: [UnorderedKey] The NEXT_PUBLIC_URL key should go before the NEXT_PUBLIC_WORDPRESS_URL key

(UnorderedKey)


[warning] 6-6: [QuoteCharacter] The value has quote characters (', ")

(QuoteCharacter)

🪛 LanguageTool
README.md

[uncategorized] ~406-~406: The official name of this content management system is spelled with a capital “P”.
Context: ..., and getCategoryByIdfunctions fromlib/wordpress.ts`. 2. It formats the post date using...

(WORDPRESS)

🪛 markdownlint-cli2 (0.18.1)
README.md

9-9: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


10-10: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


11-11: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


12-12: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


13-13: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


14-14: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


15-15: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


16-16: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


17-17: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


18-18: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


19-19: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


20-20: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)


21-21: Unordered list indentation
Expected: 0; Actual: 1

(MD007, ul-indent)

🔇 Additional comments (17)
.gitignore (1)

28-30: LGTM!

Adding .env to the ignore list aligns with the Docker and compose workflows introduced in this PR. The comment update appropriately reflects the broader scope.

dockerignore (1)

1-7: LGTM!

The .dockerignore patterns are appropriate for excluding unnecessary files from the Docker build context. Note that .env is intentionally not excluded, allowing environment files to be available during builds if needed.

Dockerfile (2)

1-28: LGTM!

The base and deps stages are well-structured. Using Alpine for a smaller image and pnpm for efficient dependency management are good choices.


52-75: Standalone output configuration is correctly set.

The next.config.ts file has output: "standalone" configured on line 46, which enables the Dockerfile to successfully copy from /app/.next/standalone during the build process.

plugin/README.md (1)

27-30: LGTM!

The environment variable rename from WORDPRESS_WEBHOOK_SECRET to NEXT_WORDPRESS_WEBHOOK_SECRET aligns with the broader naming convention changes across the project. This improves clarity by indicating these are Next.js-specific environment variables.

plugin/next-revalidate/README.txt (1)

37-37: LGTM!

The environment variable reference is correctly updated to match the new naming convention used throughout the project.

.env.example (2)

1-2: Verify example URLs are intentional.

The example file contains what appear to be real URLs (sysblok.ru and next.sysblok.team). Ensure these are intended as example values rather than placeholder URLs like example.com.


1-6: Approve environment variable naming changes.

The renaming of environment variables improves clarity:

  • NEXT_PUBLIC_WORDPRESS_URL and NEXT_PUBLIC_URL correctly use the NEXT_PUBLIC_ prefix for client-side exposure
  • NEXT_WORDPRESS_WEBHOOK_SECRET appropriately omits the NEXT_PUBLIC_ prefix to keep the secret server-side only

This aligns with Next.js best practices and the broader refactoring in this PR.

Based on learnings

app/pages/page.tsx (1)

16-16: LGTM!

Limiting the fetched fields to only ["id", "slug", "title"] improves performance by reducing the payload size from the WordPress API. This aligns with the actual data usage in the rendering logic (lines 24-26).

app/posts/page.tsx (1)

55-55: LGTM!

The parameter mapping correctly transforms URL search params (singular: tag, category) to the API function's expected parameters (plural: tags, categories). This aligns with the updated getPostsPaginated signature in lib/wordpress.ts.

next.config.ts (1)

46-46: LGTM!

The standalone output mode is appropriate for the Docker-based deployment infrastructure introduced in this PR.

CLAUDE.md (1)

62-63: LGTM!

The environment variable documentation correctly reflects the new naming convention (NEXT_PUBLIC_WORDPRESS_URL and NEXT_WORDPRESS_WEBHOOK_SECRET) used throughout the PR.

app/sitemap.ts (1)

6-6: LGTM!

Field limiting to only slug and modified is an excellent optimization that reduces the data payload for sitemap generation while fetching exactly what's needed.

components/nav/mobile-nav.tsx (1)

63-71: LGTM!

Conditional rendering prevents displaying an empty Blog Menu section, improving the user experience. This aligns with similar conditional rendering in the Footer.

compose.yml (1)

7-8: Verify the need to load both .env.example and .env.

Loading both environment files can cause confusion about which values take precedence. Docker Compose loads them in order, with .env overriding .env.example.

Typically, only .env should be loaded in development, as .env.example serves as a template for developers to create their own .env file.

Consider whether .env.example should be removed:

    env_file:
-      - .env.example
      - .env
app/pages/[slug]/page.tsx (1)

11-11: LGTM!

Returning the result directly from getAllPages is cleaner and more efficient than the previous approach. The field limitation ensures only necessary data is fetched for static path generation.

lib/utils.ts (1)

9-14: LGTM!

The defensive type checking and HTML tag stripping logic is sound.

Comment on lines +9 to +24
const newsCategpry = await getCategoryBySlug("news");
const { data: posts } = await getPostsPaginated(1, 30, { categories_exclude: newsCategpry.id });

// This is just some example TSX
const ToDelete = () => {
return (
<main className="space-y-6">
<Container>
<Prose>
<h1>
<Balancer>Headless WordPress built with the Next.js</Balancer>
Системный Блокъ
</h1>

<p>
This is <a href="https://github.com/9d8dev/next-wp">next-wp</a>,
created as a way to build WordPress sites with Next.js at rapid speed.
This starter is designed with{" "}
<a href="https://ui.shadcn.com">shadcn/ui</a>,{" "}
<a href="https://craft-ds.com">craft-ds</a>, and Tailwind CSS. Use{" "}
<a href="https://components.work">brijr/components</a> to build your
site with prebuilt components. The data fetching and typesafety is
handled in <code>lib/wordpress.ts</code> and{" "}
<code>lib/wordpress.d.ts</code>.
</p>
</Prose>

<div className="flex justify-between items-center gap-4">
{/* Vercel Clone Starter */}
<div className="flex items-center gap-3">
<a
className="h-auto block"
href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2F9d8dev%2Fnext-wp&env=WORDPRESS_URL,WORDPRESS_HOSTNAME&envDescription=Add%20WordPress%20URL%20with%20Rest%20API%20enabled%20(ie.%20https%3A%2F%2Fwp.example.com)%20abd%20the%20hostname%20for%20Image%20rendering%20in%20Next%20JS%20(ie.%20wp.example.com)&project-name=next-wp&repository-name=next-wp&demo-title=Next%20JS%20and%20WordPress%20Starter&demo-url=https%3A%2F%2Fwp.9d8.dev"
>
{/* eslint-disable-next-line */}
<img
className="not-prose my-4"
src="https://vercel.com/button"
alt="Deploy with Vercel"
width={105}
height={32.62}
/>
</a>
<p className="!text-sm sr-only sm:not-sr-only text-muted-foreground">
Deploy with Vercel in seconds.
</p>
</div>

<div className="flex gap-2 items-center">
<WordPressIcon className="text-foreground" width={32} height={32} />
<NextJsIcon className="text-foreground" width={32} height={32} />
</div>
</div>

<div className="grid md:grid-cols-3 gap-4 mt-6">
<Link
className="border h-48 bg-accent/50 rounded-lg p-4 flex flex-col justify-between hover:scale-[1.02] transition-all"
href="/posts"
>
<Pen size={32} />
<span>
Posts{" "}
<span className="block text-sm text-muted-foreground">
All posts from your WordPress
</span>
</span>
</Link>
<Link
className="border h-48 bg-accent/50 rounded-lg p-4 flex flex-col justify-between hover:scale-[1.02] transition-all"
href="/pages"
>
<File size={32} />
<span>
Pages{" "}
<span className="block text-sm text-muted-foreground">
Custom pages from your WordPress
</span>
</span>
</Link>
<Link
className="border h-48 bg-accent/50 rounded-lg p-4 flex flex-col justify-between hover:scale-[1.02] transition-all"
href="/posts/authors"
>
<User size={32} />
<span>
Authors{" "}
<span className="block text-sm text-muted-foreground">
List of the authors from your WordPress
</span>
</span>
</Link>
<Link
className="border h-48 bg-accent/50 rounded-lg p-4 flex flex-col justify-between hover:scale-[1.02] transition-all"
href="/posts/tags"
>
<Tag size={32} />
<span>
Tags{" "}
<span className="block text-sm text-muted-foreground">
Content by tags from your WordPress
</span>
</span>
</Link>
<Link
className="border h-48 bg-accent/50 rounded-lg p-4 flex flex-col justify-between hover:scale-[1.02] transition-all"
href="/posts/categories"
>
<Diamond size={32} />
<span>
Categories{" "}
<span className="block text-sm text-muted-foreground">
Categories from your WordPress
</span>
</span>
</Link>
<a
className="border h-48 bg-accent/50 rounded-lg p-4 flex flex-col justify-between hover:scale-[1.02] transition-all"
href="https://github.com/9d8dev/next-wp/blob/main/README.md"
>
<Folder size={32} />
<span>
Documentation{" "}
<span className="block text-sm text-muted-foreground">
How to use `next-wp`
</span>
</span>
</a>
</div>
</main>
{posts.length > 0 ? (
<div className="grid md:grid-cols-3 gap-4">
{posts.map((post) => (
<PostCard key={post.id} post={post} />
))}
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Guard against a missing “news” category before dereferencing.

Line 10 calls newsCategpry.id without verifying that getCategoryBySlug("news") returned a value. If the slug is absent (or the API response changes), the page throws Cannot read properties of undefined, breaking the homepage render. Please null‑check the category before building the query and only pass categories_exclude when you have an ID.

-  const newsCategpry = await getCategoryBySlug("news");
-  const { data: posts } = await getPostsPaginated(1, 30, { categories_exclude: newsCategpry.id });
+  const newsCategory = await getCategoryBySlug("news");
+  const { data: posts } = await getPostsPaginated(1, 30, {
+    ...(newsCategory?.id ? { categories_exclude: newsCategory.id } : {}),
+  });
🤖 Prompt for AI Agents
In app/page.tsx around lines 9 to 24, guard against a missing "news" category
before dereferencing: after calling getCategoryBySlug("news") check whether the
returned category is truthy and only include categories_exclude when you have an
id (e.g. build the query params conditionally or pass undefined when absent).
Update the getPostsPaginated call to use either { categories_exclude:
category.id } when category exists or omit/pass undefined when it does not,
avoiding direct access to category.id and preventing runtime crashes.

Comment on lines +33 to +47
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
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.

Comment on lines +16 to +22
export function extractExcerptText(excerpt: string): string {
if (!excerpt || typeof excerpt !== 'string') {
return '';
}
const matches = excerpt.match(/<p>([\s\S]*)<\/p>/);
return matches ? stripHtmlTags(matches[1]) : '';
}
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

Greedy regex incorrectly extracts content from multiple paragraphs.

The regex /<p>([\s\S]*)<\/p>/ uses a greedy quantifier that will match from the first <p> to the last </p> in the string. For example, if the excerpt is "<p>First</p><p>Second</p>", it will capture "First</p><p>Second" instead of just "First".

Apply this diff to use a non-greedy quantifier:

-  const matches = excerpt.match(/<p>([\s\S]*)<\/p>/);
+  const matches = excerpt.match(/<p>([\s\S]*?)<\/p>/);
📝 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
export function extractExcerptText(excerpt: string): string {
if (!excerpt || typeof excerpt !== 'string') {
return '';
}
const matches = excerpt.match(/<p>([\s\S]*)<\/p>/);
return matches ? stripHtmlTags(matches[1]) : '';
}
export function extractExcerptText(excerpt: string): string {
if (!excerpt || typeof excerpt !== 'string') {
return '';
}
const matches = excerpt.match(/<p>([\s\S]*?)<\/p>/);
return matches ? stripHtmlTags(matches[1]) : '';
}
🤖 Prompt for AI Agents
In lib/utils.ts around lines 16 to 22, the current greedy regex
/<p>([\s\S]*)<\/p>/ can capture across multiple paragraph tags; change it to a
non-greedy match so the function returns only the first paragraph (use
/<p>([\s\S]*?)<\/p>/ or equivalent), then keep returning
stripHtmlTags(matches[1]) when a match exists; no other logic changes needed.

Comment on lines +299 to +304
export interface MediaQuery extends EntityQuery<FeaturedMedia> {
parent?: number | Array<number>; // Limit result set to items with particular parent IDs.
parent_exclude?: number | Array<number>; // Limit result set to all items except those of a particular parent ID.
media_type?: "image" | "video" | "text" | "application" | "audio"; // Limit result set to attachments of a particular media type.
mime_type?: string; // Limit result set to attachments of a particular MIME type.
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Update MediaQuery to use the renamed Media type.

Line 300 still references FeaturedMedia, which no longer exists after renaming the interface. This breaks type checking for every media query helper. Please switch the generic to the new Media type.

-export interface MediaQuery extends EntityQuery<FeaturedMedia> {
+export interface MediaQuery extends EntityQuery<Media> {
📝 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
export interface MediaQuery extends EntityQuery<FeaturedMedia> {
parent?: number | Array<number>; // Limit result set to items with particular parent IDs.
parent_exclude?: number | Array<number>; // Limit result set to all items except those of a particular parent ID.
media_type?: "image" | "video" | "text" | "application" | "audio"; // Limit result set to attachments of a particular media type.
mime_type?: string; // Limit result set to attachments of a particular MIME type.
}
export interface MediaQuery extends EntityQuery<Media> {
parent?: number | Array<number>; // Limit result set to items with particular parent IDs.
parent_exclude?: number | Array<number>; // Limit result set to all items except those of a particular parent ID.
media_type?: "image" | "video" | "text" | "application" | "audio"; // Limit result set to attachments of a particular media type.
mime_type?: string; // Limit result set to attachments of a particular MIME type.
}
🤖 Prompt for AI Agents
In lib/wordpress.d.ts around lines 299 to 304, the MediaQuery interface still
uses the old generic type FeaturedMedia which was renamed to Media; update the
generic parameter from FeaturedMedia to Media so MediaQuery extends
EntityQuery<Media> instead, preserving all existing properties and imports
(ensure any import/typedef references match the new Media name).

Comment on lines +626 to +633
const res = await fetch(
`${process.env.WP_URL}/wp-json/wp/v2/coauthors?post=${postId}`
);
if (!res.ok) return [];
const coauthors = await res.json();

return coauthors.map((a: any) => a.name.replace(/^cap-/, ""));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix coauthor fetch to use configured base URL.

getCoAuthorsByPost calls fetch against ${process.env.WP_URL}, but the module now standardizes on NEXT_PUBLIC_WORDPRESS_URL (exposed as baseUrl). In production this leaves WP_URL undefined, yielding an invalid URL at runtime and breaking co-author loading. Reuse baseUrl (or the existing wordpressFetch helper) so the request honors the configured endpoint, headers, and cache tags.

 export async function getCoAuthorsByPost(postId: number) {
-  const res = await fetch(
-    `${process.env.WP_URL}/wp-json/wp/v2/coauthors?post=${postId}`
-  );
-  if (!res.ok) return [];
-  const coauthors = await res.json();
-
-  return coauthors.map((a: any) => a.name.replace(/^cap-/, ""));
+  const res = await fetch(
+    `${baseUrl}/wp-json/wp/v2/coauthors?post=${postId}`,
+    {
+      headers: { "User-Agent": "Next.js WordPress Client" },
+      next: {
+        tags: ["wordpress", "authors", `post-${postId}`],
+        revalidate: 86400,
+      },
+    }
+  );
+  if (!res.ok) return [];
+  const coauthors = await res.json();
+
+  return coauthors.map((a: any) => a.name.replace(/^cap-/, ""));
 }

Based on learnings

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In lib/wordpress.ts around lines 626 to 633, the function uses
process.env.WP_URL directly which will be undefined in production; replace that
fetch with the module's configured baseUrl or, better, call the existing
wordpressFetch helper so the request uses the standardized base URL, headers and
cache tags. Update the code to build the URL from baseUrl (or call
wordpressFetch with path `/wp-json/wp/v2/coauthors?post=${postId}`) and keep the
same response handling (return [] on non-ok and map names stripping the cap-
prefix).

Comment on lines +57 to +58
stop:
docker stop $(CONTAINER_NAME)
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.

Comment on lines +30 to +31
hostname: "sysblok.ru",
pathname: "/wp-content/**",
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

Hardcoded hostname limits flexibility across environments.

The hardcoded sysblok.ru hostname prevents using different WordPress instances for development, staging, or production. This contradicts the environment-variable-driven approach documented in CLAUDE.md.

Consider deriving the hostname from the environment variable:

-        hostname: "sysblok.ru",
-        pathname: "/wp-content/**",
+        ...new URL(`${process.env.NEXT_PUBLIC_WORDPRESS_URL}/wp-content/**`),
📝 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
hostname: "sysblok.ru",
pathname: "/wp-content/**",
hostname: new URL(process.env.NEXT_PUBLIC_WORDPRESS_URL).hostname,
pathname: "/wp-content/**",
🤖 Prompt for AI Agents
In next.config.ts around lines 30-31 the hostname is hardcoded to "sysblok.ru";
replace it with a value derived from an environment variable (e.g. use
process.env.WP_HOST or parse process.env.WP_URL to extract the hostname) and
provide a sensible default like "sysblok.ru" if the env var is absent, validate
that the resulting value is a string/valid hostname, and ensure the env var is
documented/available in your deployment/runtime so different WordPress instances
(dev/staging/prod) can be used without code changes.

Comment on lines +39 to +41
destination: `${
process.env.WORDPRESS_URL || "https://sysblok.ru"
}/wp-admin`,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Environment variable name mismatch breaks redirect.

The redirect uses WORDPRESS_URL, but CLAUDE.md documents NEXT_PUBLIC_WORDPRESS_URL as the required environment variable. This inconsistency will cause the redirect to fail.

Apply this diff:

        destination: `${
-          process.env.WORDPRESS_URL || "https://sysblok.ru"
+          process.env.NEXT_PUBLIC_WORDPRESS_URL || "https://sysblok.ru"
        }/wp-admin`,
📝 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
destination: `${
process.env.WORDPRESS_URL || "https://sysblok.ru"
}/wp-admin`,
destination: `${
process.env.NEXT_PUBLIC_WORDPRESS_URL || "https://sysblok.ru"
}/wp-admin`,
🤖 Prompt for AI Agents
In next.config.ts around lines 39 to 41, the redirect is reading
process.env.WORDPRESS_URL while the docs and app expect
NEXT_PUBLIC_WORDPRESS_URL; update the code to use
process.env.NEXT_PUBLIC_WORDPRESS_URL (keeping the same fallback
"https://sysblok.ru") so the environment variable name matches CLAUDE.md and the
redirect works reliably.

Comment on lines 375 to 395
public function on_post_trash($post_id) {
$post_type = get_post_type($post_id);
error_log("Next.js Revalidation: Post trashed {$post_id} of type {$post_type}");
$this->send_revalidation_request($post_type, $post_id);
$post = get_post($post_id);
error_log("Next.js Revalidation: Post trashed {$post_id} of type {$post->type}");
$this->send_revalidation_request($post->type, $post_id, $post->post_name);
}

public function on_post_untrash($post_id) {
$post_type = get_post_type($post_id);
error_log("Next.js Revalidation: Post untrashed {$post_id} of type {$post_type}");
$this->send_revalidation_request($post_type, $post_id);
$post = get_post($post_id);
error_log("Next.js Revalidation: Post untrashed {$post_id} of type {$post->type}");
$this->send_revalidation_request($post->type, $post_id, $post->post_name);
}

public function on_post_delete($post_id) {
$post_type = get_post_type($post_id);
if (!$post_type) {
$post_type = 'unknown'; // Fallback if post type can't be determined
public function on_post_delete($post_id, $post = null) {
// Get the post if not provided
if (null === $post) {
$post = get_post($post_id);
}
error_log("Next.js Revalidation: Post deleted {$post_id} of type {$post_type}");
$this->send_revalidation_request($post_type, $post_id);

error_log("Next.js Revalidation: Post deleted {$post_id} of type {$post->type}");
$this->send_revalidation_request($post->type, $post_id, $post->post_name);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Use post_type and guard against missing WP_Post instances.

Lines 377‑395 assume $post is always present and expose $post->type. In WordPress, the property is post_type; $post->type is undefined and results in empty payloads. Worse, when after_delete_post fires, $post can be null, so $post->type / $post->post_name trigger fatal errors. Please bail out if no WP_Post is available and use post_type to populate the payload.

     public function on_post_trash($post_id) {
-        $post = get_post($post_id);
-        error_log("Next.js Revalidation: Post trashed {$post_id} of type {$post->type}");
-        $this->send_revalidation_request($post->type, $post_id, $post->post_name);
+        $post = get_post($post_id);
+        if (!$post instanceof \WP_Post) {
+            error_log("Next.js Revalidation: Post trashed {$post_id} but WP_Post instance was unavailable");
+            return;
+        }
+        $post_type = $post->post_type;
+        error_log("Next.js Revalidation: Post trashed {$post_id} of type {$post_type}");
+        $this->send_revalidation_request($post_type, $post_id, $post->post_name);
     }
 
     public function on_post_untrash($post_id) {
-        $post = get_post($post_id);
-        error_log("Next.js Revalidation: Post untrashed {$post_id} of type {$post->type}");
-        $this->send_revalidation_request($post->type, $post_id, $post->post_name);
+        $post = get_post($post_id);
+        if (!$post instanceof \WP_Post) {
+            error_log("Next.js Revalidation: Post untrashed {$post_id} but WP_Post instance was unavailable");
+            return;
+        }
+        $post_type = $post->post_type;
+        error_log("Next.js Revalidation: Post untrashed {$post_id} of type {$post_type}");
+        $this->send_revalidation_request($post_type, $post_id, $post->post_name);
     }
 
     public function on_post_delete($post_id, $post = null) {
-        if (null === $post) {
-            $post = get_post($post_id);
-        }
-
-        error_log("Next.js Revalidation: Post deleted {$post_id} of type {$post->type}");
-        $this->send_revalidation_request($post->type, $post_id, $post->post_name);
+        if (!$post instanceof \WP_Post) {
+            $post = get_post($post_id);
+        }
+
+        if (!$post instanceof \WP_Post) {
+            error_log("Next.js Revalidation: Post deleted {$post_id} but WP_Post instance was unavailable");
+            return;
+        }
+
+        $post_type = $post->post_type;
+        error_log("Next.js Revalidation: Post deleted {$post_id} of type {$post_type}");
+        $this->send_revalidation_request($post_type, $post_id, $post->post_name);
     }
📝 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
public function on_post_trash($post_id) {
$post_type = get_post_type($post_id);
error_log("Next.js Revalidation: Post trashed {$post_id} of type {$post_type}");
$this->send_revalidation_request($post_type, $post_id);
$post = get_post($post_id);
error_log("Next.js Revalidation: Post trashed {$post_id} of type {$post->type}");
$this->send_revalidation_request($post->type, $post_id, $post->post_name);
}
public function on_post_untrash($post_id) {
$post_type = get_post_type($post_id);
error_log("Next.js Revalidation: Post untrashed {$post_id} of type {$post_type}");
$this->send_revalidation_request($post_type, $post_id);
$post = get_post($post_id);
error_log("Next.js Revalidation: Post untrashed {$post_id} of type {$post->type}");
$this->send_revalidation_request($post->type, $post_id, $post->post_name);
}
public function on_post_delete($post_id) {
$post_type = get_post_type($post_id);
if (!$post_type) {
$post_type = 'unknown'; // Fallback if post type can't be determined
public function on_post_delete($post_id, $post = null) {
// Get the post if not provided
if (null === $post) {
$post = get_post($post_id);
}
error_log("Next.js Revalidation: Post deleted {$post_id} of type {$post_type}");
$this->send_revalidation_request($post_type, $post_id);
error_log("Next.js Revalidation: Post deleted {$post_id} of type {$post->type}");
$this->send_revalidation_request($post->type, $post_id, $post->post_name);
}
public function on_post_trash($post_id) {
$post = get_post($post_id);
if (!$post instanceof \WP_Post) {
error_log("Next.js Revalidation: Post trashed {$post_id} but WP_Post instance was unavailable");
return;
}
$post_type = $post->post_type;
error_log("Next.js Revalidation: Post trashed {$post_id} of type {$post_type}");
$this->send_revalidation_request($post_type, $post_id, $post->post_name);
}
public function on_post_untrash($post_id) {
$post = get_post($post_id);
if (!$post instanceof \WP_Post) {
error_log("Next.js Revalidation: Post untrashed {$post_id} but WP_Post instance was unavailable");
return;
}
$post_type = $post->post_type;
error_log("Next.js Revalidation: Post untrashed {$post_id} of type {$post_type}");
$this->send_revalidation_request($post_type, $post_id, $post->post_name);
}
public function on_post_delete($post_id, $post = null) {
if (!$post instanceof \WP_Post) {
$post = get_post($post_id);
}
if (!$post instanceof \WP_Post) {
error_log("Next.js Revalidation: Post deleted {$post_id} but WP_Post instance was unavailable");
return;
}
$post_type = $post->post_type;
error_log("Next.js Revalidation: Post deleted {$post_id} of type {$post_type}");
$this->send_revalidation_request($post_type, $post_id, $post->post_name);
}
🤖 Prompt for AI Agents
In plugin/next-revalidate/next-revalidate.php around lines 375 to 395, the
handlers assume $post exists and use the non-existent $post->type and
$post->post_name, which can be undefined or cause fatal errors when $post is
null; update each method to first ensure $post is a WP_Post (if null, call
get_post and if still falsy return early), then use $post->post_type (not
$post->type) and $post->post_name when calling send_revalidation_request; bail
out gracefully if $post is missing so no properties are accessed on a
non-object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants