Contents
Introducción
Este tutorial explica cómo paginar comentarios de WordPress dinámicamente usando JavaScript (AJAX/Fetch) y un endpoint REST personalizado que devuelve HTML de los comentarios ya formateados. La idea es mantener la marca visual y la compatibilidad con los callbacks de comentarios de WordPress (como wp_list_comments) pero actualizar la página sin recargar por completo. El resultado: navegación fluida entre páginas de comentarios, soporte para la historia del navegador (back/forward), y carga progresiva para mejorar UX.
Requisitos y consideraciones
- WordPress 4.7 (para REST API), idealmente 5.x.
- Acceso al functions.php del tema o plugin propio para registrar endpoints y encolar scripts.
- Conocimientos básicos de JavaScript moderno (Fetch, Promises) y PHP.
- Accesibilidad: los enlaces de paginación deben ser focusables y anunciables por lectores de pantalla.
- Fallback: asegúrate de que la paginación HTML tradicional siga funcionando si JavaScript está deshabilitado.
Enfoque general
- Crear un endpoint REST que acepte parámetros: post_id, page y per_page, y que devuelva HTML con la lista de comentarios y datos de paginación (total, total_pages).
- Encolar un script JavaScript en el frontend que capture los clics en los enlaces de paginación y haga llamadas Fetch al endpoint, reemplace el contenedor de comentarios y actualice la URL con history.pushState.
- Agregar manejo de estados (cargando, error) y restaurar el estado al navegar con back/forward (window.onpopstate).
- Opcional: añadir caching simple en el cliente y optimizaciones en el servidor (transients) para evitar consultas repetidas.
1) Código del endpoint REST (PHP)
Registrar un endpoint en REST API que reciba parámetros y devuelva un array JSON con html, page, total_pages y total_comments. Este endpoint renderizará los comentarios con wp_list_comments para mantener el markup del tema.
GET, callback => custom_rest_get_comments_html, permission_callback => __return_true, // ajustar si es necesario )) }) function custom_rest_get_comments_html(WP_REST_Request request) { post_id = absint(request->get_param(post_id)) page = max(1, intval(request->get_param(page) ?: 1)) per_page = max(1, intval(request->get_param(per_page) ?: get_option(comments_per_page, 10))) order = sanitize_text_field(request->get_param(order) ?: get_option(comment_order, asc)) if (!get_post(post_id)) { return new WP_Error(invalid_post, Post not found, array(status => 404)) } // Obtener total de comentarios aprobados para ese post count_args = array( post_id => post_id, status => approve, count => true, ) total_comments = get_comments(count_args) total_pages = (int) ceil(total_comments / per_page) // Obtener comentarios para la página solicitada comments_args = array( post_id => post_id, status => approve, number => per_page, offset => (page - 1) per_page, orderby => comment_date_gmt, order => order, ) comments = get_comments(comments_args) // Renderizar los comentarios usando el walker del tema ob_start() // El container se puede personalizar en el tema aquí usamos wp_list_comments // Asegúrate que tu tema tiene el callback correcto para wp_list_comments wp_list_comments(array(style => ol, short_ping => true), comments) comments_html = ob_get_clean() // Generar HTML para la paginación (links simples) el JS puede procesarlos base = add_query_arg(array(page => %#%), get_permalink(post_id)) pagination_html = paginate_links(array( base => base, format => ?page=%#%, current => page, total => total_pages, type => list, end_size => 1, mid_size => 2, )) return rest_ensure_response(array( html => comments_html, pagination_html => pagination_html, page => page, total_pages => total_pages, total_comments => total_comments, )) } ?>
Notas sobre el endpoint
- La función wp_list_comments utiliza el callback de tu tema para renderizar cada comentario. Si tu tema no lo define, WordPress usa el por defecto.
- Se puede mejorar la seguridad restringiendo permission_callback o validando nonces si devuelve datos sensibles.
- Si prefieres devolver datos crudos (JSON) en lugar de HTML, el cliente debe renderizar los comentarios aquí optamos por HTML para preservar markup complejo.
2) Encolar scripts y pasar variables al cliente
Encolar el archivo JS y pasar las variables necesarias (endpoint URL, post ID, nonce). Usa wp_localize_script o wp_add_inline_script para inyectar configuración.
esc_url_raw(rest_url(custom/v1/comments)), nonce => wp_create_nonce(wp_rest), per_page=> (int) get_option(comments_per_page, 10), )) }) ?>
3) Código JavaScript: lógica del cliente
El JS captura clics en enlaces de paginación dentro de un contenedor (por ejemplo #comments-pagination) y solicita la nueva página al endpoint. También maneja history.pushState y window.onpopstate para navegación con botones del navegador.
// Archivo: js/comments-pager.js (function () { if (!(fetch in window)) return // Fallback simple var settings = window.CommentsPagerSettings {} var apiUrl = settings.api_url var perPage = settings.per_page 10 var commentsContainer = document.getElementById(comments) // debe existir en tema var paginationContainer = document.getElementById(comments-pagination) // donde están los links var currentPostId = commentsContainer ? commentsContainer.getAttribute(data-post-id) : null if (!commentsContainer !paginationContainer !currentPostId) return function buildUrl(page) { var url = new URL(apiUrl, window.location.origin) url.searchParams.set(post_id, currentPostId) url.searchParams.set(page, page) url.searchParams.set(per_page, perPage) return url.toString() } function setLoading(state) { if (state) { commentsContainer.classList.add(loading) // puedes insertar un spinner si quieres } else { commentsContainer.classList.remove(loading) } } function fetchPage(page, pushState) { setLoading(true) fetch(buildUrl(page), { method: GET, credentials: same-origin, headers: { X-WP-Nonce: settings.nonce } }) .then(function (res) { if (!res.ok) throw new Error(Error en la solicitud: res.status) return res.json() }) .then(function (data) { // Reemplazar HTML de comentarios commentsContainer.innerHTML =(data.html )
// Reemplazar paginación if (data.pagination_html) { paginationContainer.innerHTML = data.pagination_html } else { paginationContainer.innerHTML = } // Actualizar el estado de la URL (query param cpage por ejemplo) var newUrl = new URL(window.location.href) newUrl.searchParams.set(cpage, page) if (pushState) { history.pushState({ cpage: page }, , newUrl.toString()) } else { history.replaceState({ cpage: page }, , newUrl.toString()) } // Opcional: foco en container para a11y commentsContainer.setAttribute(tabindex, -1) commentsContainer.focus() }) .catch(function (err) { console.error(err) // mostrar mensaje de error mínimo paginationContainer.insertAdjacentHTML(beforeend,No se pudieron cargar los comentarios. Inténtalo de nuevo.
) }) .finally(function () { setLoading(false) }) } // Manejar clics delegados en el contenedor de paginación paginationContainer.addEventListener(click, function (e) { var target = e.target // Buscar el enlace hasta el ancestor while (target target !== paginationContainer target.tagName !== A) target = target.parentNode if (!target target === paginationContainer) return e.preventDefault() // Determinar página objetivo desde href o texto var href = target.getAttribute(href) var pageMatch = null if (href) { try { var u = new URL(href, window.location.origin) pageMatch = u.searchParams.get(page) u.searchParams.get(cpage) } catch (err) { // parse fallback: buscar ?page=# var m = href.match(/page=(d )/) if (m) pageMatch = m[1] } } var page = parseInt(pageMatch, 10) 1 fetchPage(page, true) }) // Manejar back/forward window.addEventListener(popstate, function (e) { var state = e.state var page = state state.cpage ? state.cpage : (new URL(window.location.href)).searchParams.get(cpage) 1 fetchPage(page, false) }) // Inicial: si URL tiene cpage, cargar esa página var initialPage = (new URL(window.location.href)).searchParams.get(cpage) 1 // Si la página inicial no es 1, solicitarla para asegurar que el HTML coincide if (parseInt(initialPage, 10) !== 1) { fetchPage(initialPage, false) } })()
Explicación de decisiones en JS
- Uso de fetch con credentials para soportar cookies si es necesario.
- Encapsulado y delegación de eventos para funcionamiento con paginaciones generadas dinámicamente.
- Uso de history.pushState para actualizar la URL sin recargar y window.onpopstate para restaurar páginas al usar atrás/adelante.
- Foco en el contenedor tras el cambio para accesibilidad.
4) Ejemplo de HTML del tema (marcadores para JS)
Tu plantilla de comentarios (comments.php o template part) debe ofrecer contenedores con identificadores utilizados por el script. Un ejemplo sencillo:
>
5) Estilos básicos (CSS)
Agregar un estilo de carga y visuales mínimos para indicar que se está cargando.
/ Ejemplo simple / #comments.loading { opacity: 0.6 position: relative } #comments.loading:after { content: Cargando... position: absolute right: 1rem top: 1rem background: rgba(0,0,0,0.6) color: #fff padding: 0.2rem 0.5rem border-radius: 3px font-size: 0.9rem } .comments-error { color: #c00 margin-top: 1rem }
6) Mejores prácticas y optimizaciones
- Cache en servidor: si tu site recibe muchas peticiones, considera usar transients para almacenar HTML de páginas de comentarios por X segundos.
- Evita consultas innecesarias: limita metadatos cargados por comentario si no los necesitas.
- Paginación accesible: usa enlaces ltagt con texto claro (Página 2) y atributos ARIA si procede.
- SEO y enlaces profundos: actualizar la URL permite enlazar a una página de comentarios concreta. También añade rel=prev/next si generas links en servidor.
- Fallback sin JS: conserva la paginación tradicional en HTML para usuarios sin JavaScript.
- Protección contra scraping: si expones datos sensibles o emails en comentarios, sanitiza y evita exponer información no permitida.
7) Manejo de errores comunes
- 500 o error REST: revisa logs PHP y confirma que el endpoint se registró correctamente y no hay errores de sintaxis.
- Comentarios vacíos: si no hay comentarios, asegúrate de que la respuesta devuelva HTML vacío y la UI muestre un mensaje amigable.
- Problemas con el markup: si el tema usa un walker personalizado, verifica que wp_list_comments esté recibiendo los argumentos correctos y que el markup resultante sea válido.
- Nonce incorrecto: si obtienes 403, revisa que estás enviando el header X-WP-Nonce y que se crea con wp_create_nonce(wp_rest).
8) Extensiones y variaciones posibles
- Infinite scroll: en lugar de paginación, cargar la siguiente página automáticamente al llegar al final.
- Renderizado parcial: devolver solo datos JSON y construir cada comentario en el cliente (útil para apps SPA o cuando necesitas reuso extremo).
- Filtrado y orden: permitir parámetros de búsqueda, autor o orden en el endpoint para funcionalidad avanzada.
- Comentarios anidados: si tu tema utiliza comentarios anidados profundos, asegúrate de que wp_list_comments preserve la estructura.
Conclusión
La paginación dinámica de comentarios en WordPress mejora significativamente la experiencia del usuario manteniendo la semántica y el markup original del tema cuando el servidor devuelve HTML. Con un endpoint REST personalizado, un script cliente que gestione Fetch y pushState, y un fallback HTML tradicional, obtendrás una solución robusta, accesible y amigable para SEO. Implementa cache en servidor y validaciones de seguridad según sea necesario para cargas altas o requisitos específicos.
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |