{"id":48798,"date":"2022-03-21T08:32:09","date_gmt":"2022-03-21T08:32:09","guid":{"rendered":"\/tutorials\/?p=48798"},"modified":"2026-09-17T01:47:08","modified_gmt":"2026-09-17T01:47:08","slug":"wordpress-react","status":"publish","type":"post","link":"\/au\/tutorials\/wordpress-react\/","title":{"rendered":"How to use React with WordPress"},"content":{"rendered":"<p class=\"wp-block-paragraph\">To use React with WordPress, <strong>keep WordPress as your content management system (CMS), then use its REST API to fetch and display your content in a separate React front end<\/strong>.<\/p><p class=\"wp-block-paragraph\">In this WordPress-React setup, you build the front end on your computer using Next.js as the React framework, then deploy it to your web app hosting provider.<\/p><p class=\"wp-block-paragraph\">Here are eight steps to integrate WordPress with React:<\/p><ol class=\"wp-block-list\">\n<li><strong>Prepare WordPress as a content back end<\/strong>. Set your permalinks and confirm that the built-in REST API returns your published posts.<\/li>\n\n\n\n<li><strong>Create your Next.js project<\/strong>. Generate the app, run it locally, and store your WordPress REST API URL in an environment variable.<\/li>\n\n\n\n<li><strong>Fetch and display WordPress posts<\/strong>. Request your published posts through the REST API and show their titles and excerpts on your Next.js home page.<\/li>\n\n\n\n<li><strong>Add individual post pages<\/strong>. Create dynamic routes that use each WordPress post slug to fetch and display the matching post.<\/li>\n\n\n\n<li><strong>Display images and custom content<\/strong>. Add featured images and expose the custom post types or custom fields your Next.js app needs.<\/li>\n\n\n\n<li><strong>Add authentication for protected content<\/strong>. Use a WordPress Application Password when your app needs drafts, private posts, or write access.<\/li>\n\n\n\n<li><strong>Add SEO metadata and a sitemap<\/strong>. Create page titles and meta descriptions for your posts, then generate a sitemap for your public URLs.<\/li>\n\n\n\n<li><strong>Deploy your Next.js app<\/strong>. Publish the app to a web app hosting, connect your domain, and configure its environment variables.<\/li>\n<\/ol><p class=\"wp-block-paragraph\"><\/p><h2 class=\"wp-block-heading h-t-title-2\" id=\"h-how-does-react-integrate-with-wordpress\">How does React integrate with WordPress?<\/h2><p class=\"wp-block-paragraph\">React integrates with WordPress in three main ways: <strong>as a separate headless front end, inside Gutenberg blocks and plugins, or as React components inside a traditional WordPress theme<\/strong>.<\/p><p class=\"wp-block-paragraph\">With a <a href=\"\/au\/tutorials\/headless-wordpress\/\">headless WordPress setup<\/a>, you keep managing content in WordPress while Next.js retrieves that content through an API and renders your public site.<\/p><p class=\"wp-block-paragraph\">WordPress provides a built-in REST API, a set of web addresses that return your posts, pages, and media as plain data instead of finished pages, so you don&rsquo;t need an extra plugin to connect it to Next.js.<\/p><p class=\"wp-block-paragraph\">You handle navigation, page templates, metadata, and previews in your Next.js app because it replaces your WordPress theme. Plugins that depend on theme output won&rsquo;t automatically carry over to your front end.<\/p><p class=\"wp-block-paragraph\">Alternatively, use React inside WordPress for custom Gutenberg blocks, editor interfaces, or plugin screens when you don&rsquo;t need a separate front end. WordPress continues to handle your theme, URLs, public pages, and most plugin behavior.<\/p><p class=\"wp-block-paragraph\">You can also keep your traditional WordPress theme and add React only to the parts that need more interactivity. This approach works well for advanced filters, calculators, dashboards, and search tools.<\/p><figure tabindex=\"0\" class=\"wp-block-table\"><table><tbody><tr><td><strong>Approach<\/strong><\/td><td><strong>How it works<\/strong><\/td><td><strong>Front-end speed<\/strong><\/td><td><strong>Development effort<\/strong><\/td><td><strong>SEO handling<\/strong><\/td><td><strong>Plugin compatibility<\/strong><\/td><\/tr><tr><td>Headless WordPress<\/td><td>Your Next.js app gets content from WordPress through an API<\/td><td>High, depending on how you render and cache pages<\/td><td>High<\/td><td>You handle rendering and metadata in Next.js<\/td><td>Low, especially for plugins that depend on theme output<\/td><\/tr><tr><td>Gutenberg and plugins<\/td><td>You use React inside the WordPress editor or plugin interfaces<\/td><td>Depends on your WordPress theme<\/td><td>Medium<\/td><td>WordPress handles your public pages<\/td><td>High<\/td><\/tr><tr><td>React in a theme<\/td><td>You add React components to PHP-rendered WordPress pages<\/td><td>Depends on your theme and React code<\/td><td>Medium<\/td><td>WordPress handles most SEO output<\/td><td>Generally high<\/td><\/tr><\/tbody><\/table><\/figure><p class=\"wp-block-paragraph\">This tutorial uses the headless WordPress approach, with Next.js as the front end and the built-in REST API connecting it to your WordPress content.<\/p><h2 class=\"wp-block-heading h-t-title-2\" id=\"h-what-to-prepare-before-integrating-react-with-wordpress\">What to prepare before integrating React with WordPress<\/h2><p class=\"wp-block-paragraph\">Before integrating React with WordPress, <strong>prepare your WordPress site, hosting for your Next.js app, Node.js with npm, and a code editor<\/strong>.<\/p><p class=\"wp-block-paragraph\">Start with a running WordPress site and <a href=\"\/au\/tutorials\/how-to-add-a-new-post-in-wordpress\/\">publish at least one post<\/a> with a featured image. You&rsquo;ll use this content to test the Next.js app later.<\/p><p class=\"wp-block-paragraph\">You can keep WordPress on your current hosting provider, but you&rsquo;ll need a separate <a href=\"\/au\/web-apps-hosting\">web app hosting plan<\/a> for your Next.js app. Get one from Hostinger or another provider that supports Node.js apps.<\/p><p class=\"wp-block-paragraph\">Next, install Node.js 20.9 or newer on your computer from <a href=\"https:\/\/nodejs.org\/en\/download\" target=\"_blank\" rel=\"noopener\">its official website<\/a>. npm comes bundled with Node.js, so you don&rsquo;t need to add it separately.<\/p><p class=\"wp-block-paragraph\">You&rsquo;ll also need a code editor such as Visual Studio Code (VS Code) to develop your Next.js front end.<\/p><figure class=\"wp-block-image size-full\"><a class=\"hgr-tutorials-cta hgr-tutorials-cta-web-hosting\" href=\"\/au\/web-hosting\" target=\"_blank\" rel=\"noreferrer noopener\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"300\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2023\/11\/Web-hosting_in-text-banner.png\/public\" alt=\"Hostinger web hosting banner\" class=\"wp-image-98604\"  sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure><h2 class=\"wp-block-heading h-t-title-2\" id=\"h-1-set-up-wordpress-as-a-headless-content-back-end\">1. Set up WordPress as a headless content back end<\/h2><p class=\"wp-block-paragraph\">Set up WordPress as a headless content back end by switching your permalinks to <strong>Post name<\/strong> and confirming that the <a href=\"\/au\/tutorials\/wordpress-rest-api\/\">WordPress REST API<\/a> returns your published posts.<\/p><p class=\"wp-block-paragraph\">To do this, open your WordPress dashboard and go to <strong>Settings &rarr; Permalinks<\/strong>. Under <strong>Permalink structure<\/strong>, choose <strong>Post name<\/strong>, then hit <strong>Save Changes<\/strong>.<\/p><div class=\"wp-block-image\"><figure data-wp-context='{\"imageId\":\"6aac20628464c\"}' data-wp-interactive=\"core\/image\" data-wp-key=\"6aac20628464c\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2026\/09\/wordpress-permalink-settings-post-name.png\/w=1024,h=1024,fit=scale-down\" alt=\"WordPress Permalink Settings page with the Post name option selected under Permalink structure.\" class=\"wp-image-157100\" title=\"wordpress-permalink-settings-post-name\"><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" data-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on--click=\"actions.showLightbox\" data-wp-style--right=\"state.thisImage.buttonRight\" data-wp-style--top=\"state.thisImage.buttonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><p class=\"wp-block-paragraph\">Next, open the following URL in your browser, replacing <strong>domain.tld<\/strong> with your WordPress domain:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">https:\/\/domain.tld\/wp-json\/wp\/v2\/posts<\/pre><p class=\"wp-block-paragraph\">You&rsquo;ll see your published posts in JSON format &ndash; a plain-text data format that React can read &ndash; when the REST API is working correctly.<\/p><div class=\"wp-block-image\"><figure data-wp-context='{\"imageId\":\"6aac2062892c6\"}' data-wp-interactive=\"core\/image\" data-wp-key=\"6aac2062892c6\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2026\/09\/wordpress-rest-api-posts-json-response.png\/w=1024,h=1024,fit=scale-down\" alt=\"WordPress REST API posts endpoint displaying a published post in JSON format with fields such as ID, slug, title, content, excerpt, featured media, categories, and tags.\" class=\"wp-image-157101\" title=\"wordpress-rest-api-posts-json-response\"><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" data-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on--click=\"actions.showLightbox\" data-wp-style--right=\"state.thisImage.buttonRight\" data-wp-style--top=\"state.thisImage.buttonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><p class=\"wp-block-paragraph\">Double-check your permalink settings and review your security plugin or server configuration if the endpoint returns an error.<\/p><h2 class=\"wp-block-heading h-t-title-2\" id=\"h-2-create-a-next-js-project\">2. Create a Next.js project<\/h2><p class=\"wp-block-paragraph\">To create your Next.js project, use <strong>create-next-app<\/strong> to generate a new project with the App Router, then start its local development server.<\/p><p class=\"wp-block-paragraph\">Open a terminal on your computer, then run:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">npx create-next-app@latest wordpress-react-frontend --js --eslint --app --no-tailwind --no-src-dir --use-npm --yes\ncd wordpress-react-frontend\nnpm run dev<\/pre><p class=\"wp-block-paragraph\">The <strong>&ndash;app<\/strong> flag enables the Next.js App Router, which you&rsquo;ll use later to create the post index and individual post routes.<\/p><div class=\"wp-block-image\"><figure data-wp-context='{\"imageId\":\"6aac20628e620\"}' data-wp-interactive=\"core\/image\" data-wp-key=\"6aac20628e620\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2026\/09\/nextjs-development-server-localhost.png\/w=1024,h=1024,fit=scale-down\" alt=\"Terminal showing the wordpress-react-frontend Next.js project created successfully and the local development server running at localhost:3000.\" class=\"wp-image-157090\" title=\"nextjs-development-server-localhost\"><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" data-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on--click=\"actions.showLightbox\" data-wp-style--right=\"state.thisImage.buttonRight\" data-wp-style--top=\"state.thisImage.buttonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><p class=\"wp-block-paragraph\">Open <strong>http:\/\/localhost:3000<\/strong> in your browser. You should see the default Next.js page.<\/p><div class=\"wp-block-image\"><figure data-wp-context='{\"imageId\":\"6aac206293306\"}' data-wp-interactive=\"core\/image\" data-wp-key=\"6aac206293306\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2026\/09\/nextjs-localhost-default-page.png\/w=1024,h=1024,fit=scale-down\" alt=\"Default Next.js page running locally at localhost:3000 with instructions to edit the page.js file.\" class=\"wp-image-157094\" title=\"nextjs-localhost-default-page\"><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" data-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on--click=\"actions.showLightbox\" data-wp-style--right=\"state.thisImage.buttonRight\" data-wp-style--top=\"state.thisImage.buttonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><p class=\"wp-block-paragraph\">Next, open VS Code and go to <strong>File &rarr; Open Folder&hellip;<\/strong>, then select the <strong>wordpress-react-frontend<\/strong> project folder. In the project root folder, click <strong>New File<\/strong> and name it <strong>.env.local<\/strong>.<\/p><p class=\"wp-block-paragraph\">This file stores your WordPress API URL as an environment variable, a setting kept outside your code so you can change it without editing your files. Next.js reads<strong> .env.local<\/strong> on the server only, so the value never reaches visitors&rsquo; browsers..<\/p><p class=\"wp-block-paragraph\">Add the following value and replace <strong>domain.tld<\/strong> with your own domain:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">WORDPRESS_API_URL=https:\/\/domain.tld\/wp-json\/wp\/v2<\/pre><div class=\"wp-block-image\"><figure data-wp-context='{\"imageId\":\"6aac206298427\"}' data-wp-interactive=\"core\/image\" data-wp-key=\"6aac206298427\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2026\/09\/vscode-nextjs-env-local-wordpress-api-url.png\/w=1024,h=1024,fit=scale-down\" alt=\"Visual Studio Code showing the .env.local file in the Next.js project root with the WordPress REST API base URL configured.\" class=\"wp-image-157098\" title=\"vscode-nextjs-env-local-wordpress-api-url\"><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" data-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on--click=\"actions.showLightbox\" data-wp-style--right=\"state.thisImage.buttonRight\" data-wp-style--top=\"state.thisImage.buttonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><p class=\"wp-block-paragraph\">Save <strong>.env.local<\/strong>, then return to your terminal and press <strong>Ctrl+C<\/strong> to stop the development server. Restart the server, so Next.js loads the new environment variable:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">npm run dev<\/pre><h2 class=\"wp-block-heading h-t-title-2\" id=\"h-3-fetch-and-display-wordpress-posts\">3. Fetch and display WordPress posts<\/h2><p class=\"wp-block-paragraph\">To fetch and display your WordPress posts, update <strong>app\/page.js<\/strong> so it requests them through the REST API and shows their titles and excerpts on your Next.js home page.<\/p><p class=\"wp-block-paragraph\">Open the <strong>app\/page.js <\/strong>file, then replace its contents with:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">async function getPosts() {\n   const response = await fetch(\n      `${process.env.WORDPRESS_API_URL}\/posts?_fields=id,title,excerpt`,\n      { cache: 'no-store' }\n   );\n\n   if (!response.ok) {\n      throw new Error(`Failed to fetch posts: ${response.status}`);\n   }\n\n   return response.json();\n}\n\nexport default async function Home() {\n   const posts = await getPosts();\n\n   return (\n      &lt;main&gt;\n         &lt;h1&gt;Latest posts&lt;\/h1&gt;\n\n         {posts.map((post) =&gt; (\n            &lt;article key={post.id}&gt;\n               &lt;h2\n                  dangerouslySetInnerHTML={{\n                     __html: post.title.rendered,\n                  }}\n               \/&gt;\n\n               &lt;div\n                  dangerouslySetInnerHTML={{\n                     __html: post.excerpt.rendered,\n                  }}\n               \/&gt;\n            &lt;\/article&gt;\n         ))}\n      &lt;\/main&gt;\n   );\n}<\/pre><p class=\"wp-block-paragraph\">Save the file, then reload <strong>http:\/\/localhost:3000<\/strong>. You&rsquo;ll see your published WordPress posts with their titles and excerpts.<\/p><div class=\"wp-block-image\"><figure data-wp-context='{\"imageId\":\"6aac20629d0f2\"}' data-wp-interactive=\"core\/image\" data-wp-key=\"6aac20629d0f2\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2026\/09\/nextjs-home-page-wordpress-posts.png\/w=1024,h=1024,fit=scale-down\" alt=\"Next.js home page showing a Latest posts heading with a WordPress post title and excerpt fetched through the REST API.\" class=\"wp-image-157093\" title=\"nextjs-home-page-wordpress-posts\"><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" data-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on--click=\"actions.showLightbox\" data-wp-style--right=\"state.thisImage.buttonRight\" data-wp-style--top=\"state.thisImage.buttonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><p class=\"wp-block-paragraph\">WordPress returns the titles and excerpts with HTML markup. The <strong>dangerouslySetInnerHTML<\/strong> property tells React to render that markup instead of displaying the HTML tags as text.<\/p><p class=\"wp-block-paragraph\">The <strong>cache: &lsquo;no-store&rsquo;<\/strong> option makes Next.js request fresh post data from WordPress on every request, so newly published posts appear after you reload the page.<\/p><p>    <div class=\"announcement-block announcement-block--warning\">\n        <span class=\"announcement-block__heading\">\n            <svg width=\"24\" height=\"26\" viewBox=\"0 0 24 26\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n                d=\"M12 23.5C17.799 23.5 22.5 18.799 22.5 13C22.5 7.20101 17.799 2.5 12 2.5C6.20101 2.5 1.5 7.20101 1.5 13C1.5 18.799 6.20101 23.5 12 23.5ZM13.637 8.65198C13.637 7.74791 12.9041 7.01501 12 7.01501C11.0959 7.01501 10.363 7.74791 10.363 8.65198C10.5335 10.5375 10.875 14.383 10.875 14.383C10.875 15.0043 11.3787 15.508 12 15.508C12.6213 15.508 13.125 15.0043 13.125 14.383V14.38L13.637 8.65198ZM11.9927 16.714C11.3714 16.714 10.8677 17.2177 10.8677 17.839C10.8677 18.4603 11.3714 18.964 11.9927 18.964H12.0073C12.6286 18.964 13.1323 18.4603 13.1323 17.839C13.1323 17.2177 12.6286 16.714 12.0073 16.714H11.9927Z\"\n                fill=\"#BE1025\"\/>\n            <\/svg>\n            Warning        <\/span>\n        <p class=\"announcement-block__content\">\n            Use <strong>dangerouslySetInnerHTML<\/strong> carefully. Only use it with content from you or people you trust, and make sure it doesn&rsquo;t contain unsafe code. Otherwise, attackers could exploit it to run malicious scripts through cross-site scripting (XSS).        <\/p>\n    <\/div>\n\n    \n\n\n\n<\/p><h2 class=\"wp-block-heading h-t-title-2\" id=\"h-4-add-dynamic-routes-for-individual-posts\">4. Add dynamic routes for individual posts<\/h2><p class=\"wp-block-paragraph\">To add dynamic routes for individual WordPress posts, create <strong>app\/posts\/[slug]\/page.js<\/strong>, add code that reads the slug from the URL and fetches the matching post from WordPress, then update <strong>app\/page.js<\/strong> to link each post title to its dynamic URL.<\/p><p class=\"wp-block-paragraph\">Create a <strong>posts<\/strong> folder inside <strong>app<\/strong>. Inside <strong>posts<\/strong>, create a <strong>[slug]<\/strong> folder, then add a <strong>page.js<\/strong> file.<\/p><p class=\"wp-block-paragraph\">Your project structure should now look like this:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">wordpress-react-frontend\/\n   app\/\n      page.js\n      posts\/\n         [slug]\/\n            page.js\n   .env.local\n   package.json<\/pre><p class=\"wp-block-paragraph\">Open <strong>app\/posts\/[slug]\/page.js<\/strong> and add:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import { notFound } from 'next\/navigation';\n\nasync function getPost(slug) {\n   const response = await fetch(\n      `${process.env.WORDPRESS_API_URL}\/posts?slug=${encodeURIComponent(slug)}&amp;_fields=id,slug,title,content`,\n      { cache: 'no-store' }\n   );\n\n   if (!response.ok) {\n      throw new Error(`Failed to fetch post: ${response.status}`);\n   }\n\n   const posts = await response.json();\n\n   return posts[0] ?? null;\n}\n\nexport default async function PostPage({ params }) {\n   const { slug } = await params;\n   const post = await getPost(slug);\n\n   if (!post) {\n      notFound();\n   }\n\n   return (\n      &lt;article&gt;\n         &lt;h1\n            dangerouslySetInnerHTML={{\n               __html: post.title.rendered,\n            }}\n         \/&gt;\n\n         &lt;div\n            dangerouslySetInnerHTML={{\n               __html: post.content.rendered,\n            }}\n         \/&gt;\n      &lt;\/article&gt;\n   );\n}<\/pre><p class=\"wp-block-paragraph\">The <strong>[slug]<\/strong> folder lets Next.js use different post slugs in the same route. A slug is the readable part of the WordPress post URL, such as <strong>sample-post<\/strong> in <strong>domain.tld\/sample-post<\/strong>.<\/p><p class=\"wp-block-paragraph\">In this case, opening <strong>\/posts\/sample-post<\/strong> gives Next.js the slug <strong>sample-post<\/strong>, which <strong>getPost()<\/strong> then uses to request that specific post from the WordPress REST API.<\/p><p class=\"wp-block-paragraph\">The <strong>notFound()<\/strong> function displays a 404 page when WordPress doesn&rsquo;t return a post with that slug.<\/p><p class=\"wp-block-paragraph\">Next, open <strong>app\/page.js<\/strong> and replace its contents with the following code. It retrieves each post&rsquo;s slug from WordPress and uses it to link the post title to its individual page:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import Link from 'next\/link';\n\nasync function getPosts() {\n   const response = await fetch(\n      `${process.env.WORDPRESS_API_URL}\/posts?_fields=id,slug,title,excerpt`,\n      { cache: 'no-store' }\n   );\n\n   if (!response.ok) {\n      throw new Error(`Failed to fetch posts: ${response.status}`);\n   }\n\n   return response.json();\n}\n\nexport default async function Home() {\n   const posts = await getPosts();\n\n   return (\n      &lt;main&gt;\n         &lt;h1&gt;Latest posts&lt;\/h1&gt;\n\n         {posts.map((post) =&gt; (\n            &lt;article key={post.id}&gt;\n               &lt;h2&gt;\n                  &lt;Link href={`\/posts\/${post.slug}`}&gt;\n                     &lt;span\n                        dangerouslySetInnerHTML={{\n                           __html: post.title.rendered,\n                        }}\n                     \/&gt;\n                  &lt;\/Link&gt;\n               &lt;\/h2&gt;\n\n               &lt;div\n                  dangerouslySetInnerHTML={{\n                     __html: post.excerpt.rendered,\n                  }}\n               \/&gt;\n            &lt;\/article&gt;\n         ))}\n      &lt;\/main&gt;\n   );\n}<\/pre><p class=\"wp-block-paragraph\">Save both <strong>app\/posts\/[slug]\/page.js<\/strong> and <strong>app\/page.js<\/strong>, then reload <strong>http:\/\/localhost:3000<\/strong>.<\/p><p class=\"wp-block-paragraph\">Click a post title to open the corresponding WordPress post at a URL such as <strong>\/posts\/sample-post<\/strong>.<\/p><div class=\"wp-block-image\"><figure data-wp-context='{\"imageId\":\"6aac2062a237f\"}' data-wp-interactive=\"core\/image\" data-wp-key=\"6aac2062a237f\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2026\/09\/nextjs-post-page-wordpress-content.png\/w=1024,h=1024,fit=scale-down\" alt=\"Next.js individual post page displaying the full content of a WordPress post, including its title, headings, and paragraphs.\" class=\"wp-image-157095\" title=\"nextjs-post-page-wordpress-content\"><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" data-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on--click=\"actions.showLightbox\" data-wp-style--right=\"state.thisImage.buttonRight\" data-wp-style--top=\"state.thisImage.buttonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><h2 class=\"wp-block-heading h-t-title-2\" id=\"h-5-display-featured-images-and-custom-content\">5. Display featured images and custom content<\/h2><p class=\"wp-block-paragraph\">Display featured images and expose custom content from WordPress by updating <strong>app\/page.js<\/strong> to render featured images and making your custom post types and custom fields available through the REST API.<\/p><p class=\"wp-block-paragraph\">For featured images, open <strong>app\/page.js<\/strong>. Inside the existing <strong>getPosts()<\/strong> function, find this <strong>fetch()<\/strong> request:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">const response = await fetch(\n   `${process.env.WORDPRESS_API_URL}\/posts?_fields=id,slug,title,excerpt`,\n   { cache: 'no-store' }\n);<\/pre><p class=\"wp-block-paragraph\">Replace that request with:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">const response = await fetch(\n   `${process.env.WORDPRESS_API_URL}\/posts?_embed=wp:featuredmedia&amp;_fields=id,slug,title,excerpt,_links,_embedded`,\n   { cache: 'no-store' }\n);<\/pre><p class=\"wp-block-paragraph\">The <strong>_embed=wp:featuredmedia<\/strong> parameter includes each post&rsquo;s featured image data in the REST API response.<\/p><p class=\"wp-block-paragraph\">Still in <strong>app\/page.js<\/strong>, find the existing <strong>posts.map((post)<\/strong> block inside the <strong>Home() <\/strong>function. Replace that entire block with:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">{posts.map((post) =&gt; {\n   const featuredImage =\n      post._embedded?.['wp:featuredmedia']?.[0];\n\n   return (\n      &lt;article key={post.id}&gt;\n         {featuredImage?.source_url &amp;&amp; (\n            &lt;img\n               src={featuredImage.source_url}\n               alt={featuredImage.alt_text || ''}\n               width={featuredImage.media_details?.width || 1200}\n               height={featuredImage.media_details?.height || 630}\n            \/&gt;\n         )}\n\n         &lt;h2&gt;\n            &lt;Link href={`\/posts\/${post.slug}`}&gt;\n               &lt;span\n                  dangerouslySetInnerHTML={{\n                     __html: post.title.rendered,\n                  }}\n               \/&gt;\n            &lt;\/Link&gt;\n         &lt;\/h2&gt;\n\n         &lt;div\n            dangerouslySetInnerHTML={{\n               __html: post.excerpt.rendered,\n            }}\n         \/&gt;\n      &lt;\/article&gt;\n   );\n})}<\/pre><p class=\"wp-block-paragraph\">You&rsquo;ll now see featured images on your home page for posts that have one assigned.<\/p><div class=\"wp-block-image\"><figure data-wp-context='{\"imageId\":\"6aac2062a7b78\"}' data-wp-interactive=\"core\/image\" data-wp-key=\"6aac2062a7b78\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2026\/09\/nextjs-home-page-wordpress-featured-image.png\/w=1024,h=1024,fit=scale-down\" alt=\"Next.js home page displaying a WordPress post with its featured image, title, and excerpt under Latest posts.\" class=\"wp-image-157092\" title=\"nextjs-home-page-wordpress-featured-image\"><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" data-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on--click=\"actions.showLightbox\" data-wp-style--right=\"state.thisImage.buttonRight\" data-wp-style--top=\"state.thisImage.buttonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><p class=\"wp-block-paragraph\">The setup for custom post types depends on how you created them in WordPress. For a custom post type you registered with PHP, edit the file that contains <strong>register_post_type()<\/strong>. This file is usually a custom plugin file or your child theme&rsquo;s <strong>functions.php<\/strong>.<\/p><p class=\"wp-block-paragraph\">Open your WordPress host&rsquo;s file manager, locate that file, and add <strong>&lsquo;show_in_rest&rsquo; =&gt; true<\/strong> to the existing post type settings. For example:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">register_post_type(\n   'book',\n   array(\n      'label'        =&gt; 'Books',\n      'public'       =&gt; true,\n      'show_in_rest' =&gt; true,\n   )\n);<\/pre><p class=\"wp-block-paragraph\">After you save the PHP file, access the <strong>book<\/strong> post type at:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">https:\/\/domain.tld\/wp-json\/wp\/v2\/book<\/pre><p>        <div class=\"protip\">\n            <div class=\"protip__heading\">\n                <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                    <path d=\"M1.49234 23.5024C1.23229 23.5024 0.972242 23.4024 0.782206 23.2123C0.562165 22.9923 0.452144 22.6822 0.502153 22.3722C0.562165 21.9221 1.14227 17.9113 3.00262 16.351C3.63274 15.8209 4.43289 15.5509 5.26305 15.5609C6.09321 15.5909 6.87335 15.9109 7.47347 16.4911C8.6937 17.6913 8.76371 19.6717 7.6435 20.9919C6.0832 22.8523 2.08245 23.4324 1.63237 23.4924C1.59236 23.4924 1.54235 23.4924 1.50234 23.4924L1.49234 23.5024ZM5.16303 17.5613C4.84297 17.5613 4.53291 17.6713 4.29287 17.8813C3.60274 18.4614 3.07264 19.9317 2.75258 21.242C4.06282 20.9219 5.5331 20.3918 6.11321 19.7017C6.55329 19.1716 6.54329 18.3814 6.0832 17.9213C5.85316 17.7013 5.5431 17.5713 5.20304 17.5613C5.19304 17.5613 5.17303 17.5613 5.16303 17.5613ZM11.7243 21.8821C11.4942 21.8821 11.2642 21.8021 11.0841 21.652C10.8541 21.462 10.7241 21.1819 10.7241 20.8819V15.9109L8.08358 13.2705H3.11264C2.81259 13.2705 2.53254 13.1404 2.3425 12.9104C2.15246 12.6803 2.07245 12.3803 2.12246 12.0902C2.19247 11.7102 2.84259 8.36953 4.70294 7.12929C6.33325 6.04909 8.96375 6.49918 10.244 6.80923C11.5442 4.96889 13.2546 3.4286 15.2349 2.33839C17.4553 1.11816 19.9858 0.518051 22.4963 0.498047C23.0464 0.498047 23.4865 0.948132 23.4865 1.49824C23.4865 5.0389 22.3763 9.97983 17.1753 13.7605C17.4853 15.0408 17.9354 17.6613 16.8552 19.2816C15.615 21.1419 12.2744 21.7921 11.8943 21.8621C11.8343 21.8721 11.7743 21.8821 11.7143 21.8821H11.7243ZM12.7245 16.181V19.6016C13.7146 19.2916 14.7948 18.7915 15.2049 18.1814C15.675 17.4812 15.605 16.091 15.385 14.9008C14.5248 15.3808 13.6346 15.8109 12.7245 16.181ZM9.66388 12.0302L11.9643 14.3307C13.1845 13.8306 14.3648 13.2204 15.485 12.5103C19.9358 9.51974 21.2361 5.60901 21.4561 2.53843C19.6157 2.67846 17.8254 3.20856 16.2051 4.09872C14.2847 5.14892 12.6544 6.68921 11.4942 8.54956C10.7841 9.65977 10.174 10.82 9.66388 12.0302ZM4.39289 11.2701H7.81353C8.1936 10.3599 8.63368 9.46974 9.11377 8.60957C7.92355 8.38953 6.51329 8.31952 5.81315 8.78961C5.19304 9.19968 4.70294 10.3099 4.39289 11.2701Z\" fill=\"#673DE6\"\/>\n                <\/svg>\n                <p class=\"protip__title\">\n                    Expose ACF fields to Next.js                <\/p>\n            <\/div>\n            <p class=\"protip__content\"> To expose custom fields you added using the Advanced Custom Fields (ACF) plugin, such as prices, subtitles, or author details, go to <strong>ACF &rarr; Field Groups<\/strong>. Open your field group, enable <strong>Show in REST API<\/strong> under <strong>Group Settings<\/strong>, then save your changes.<\/p>\n                    <\/div>\n        \n\n\n\n<\/p><h2 class=\"wp-block-heading h-t-title-2\" id=\"h-6-add-authentication-for-protected-wordpress-content\">6. Add authentication for protected WordPress content<\/h2><p class=\"wp-block-paragraph\">To add authentication for protected WordPress content, use a WordPress Application Password in your server-side Next.js requests.<\/p><p class=\"wp-block-paragraph\">Skip this step if your Next.js app only displays public content, since WordPress already allows public access to published content via the REST API.<\/p><p class=\"wp-block-paragraph\">However, you&rsquo;ll need authentication to retrieve drafts or private posts, or to create, edit, or delete WordPress content. For those requests, use the Application Password instead of your main account password.<\/p><p class=\"wp-block-paragraph\">In your WordPress dashboard, go to <strong>Users &rarr; Profile<\/strong> and scroll to <strong>Application Passwords<\/strong>. Enter a name such as <strong>Next.js app<\/strong>, then generate a new password.<\/p><p class=\"wp-block-paragraph\">This section only appears on sites using HTTPS, so check that your WordPress site has a valid SSL certificate if you don&rsquo;t see it.<\/p><div class=\"wp-block-image\"><figure data-wp-context='{\"imageId\":\"6aac2062ad528\"}' data-wp-interactive=\"core\/image\" data-wp-key=\"6aac2062ad528\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2026\/09\/wordpress-application-password-nextjs-app.png\/w=1024,h=1024,fit=scale-down\" alt=\"WordPress Application Passwords section showing a newly generated password for the Next.js app and its revoke option.\" class=\"wp-image-157099\" title=\"wordpress-application-password-nextjs-app\"><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" data-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on--click=\"actions.showLightbox\" data-wp-style--right=\"state.thisImage.buttonRight\" data-wp-style--top=\"state.thisImage.buttonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><p class=\"wp-block-paragraph\">Return to VS Code and add your WordPress username and new Application Password to <strong>.env.local<\/strong>:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">WORDPRESS_API_USERNAME=your-username\nWORDPRESS_API_PASSWORD=your-application-password<\/pre><p class=\"wp-block-paragraph\">For example, an authenticated function that retrieves draft posts would look like this:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">async function getDraftPosts() {\n   const credentials = Buffer.from(\n      `${process.env.WORDPRESS_API_USERNAME}:${process.env.WORDPRESS_API_PASSWORD}`\n   ).toString('base64');\n\n   const response = await fetch(\n      `${process.env.WORDPRESS_API_URL}\/posts?status=draft&amp;_fields=id,slug,title,excerpt`,\n      {\n         headers: {\n            Authorization: `Basic ${credentials}`,\n         },\n         cache: 'no-store',\n      }\n   );\n\n   if (!response.ok) {\n      throw new Error(`Failed to fetch drafts: ${response.status}`);\n   }\n\n   return response.json();\n}<\/pre><p class=\"wp-block-paragraph\">This sends your WordPress username and Application Password with the request so WordPress returns drafts instead of only published posts.<\/p><p>    <div class=\"announcement-block announcement-block--warning\">\n        <span class=\"announcement-block__heading\">\n            <svg width=\"24\" height=\"26\" viewBox=\"0 0 24 26\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                <path fill-rule=\"evenodd\" clip-rule=\"evenodd\"\n                d=\"M12 23.5C17.799 23.5 22.5 18.799 22.5 13C22.5 7.20101 17.799 2.5 12 2.5C6.20101 2.5 1.5 7.20101 1.5 13C1.5 18.799 6.20101 23.5 12 23.5ZM13.637 8.65198C13.637 7.74791 12.9041 7.01501 12 7.01501C11.0959 7.01501 10.363 7.74791 10.363 8.65198C10.5335 10.5375 10.875 14.383 10.875 14.383C10.875 15.0043 11.3787 15.508 12 15.508C12.6213 15.508 13.125 15.0043 13.125 14.383V14.38L13.637 8.65198ZM11.9927 16.714C11.3714 16.714 10.8677 17.2177 10.8677 17.839C10.8677 18.4603 11.3714 18.964 11.9927 18.964H12.0073C12.6286 18.964 13.1323 18.4603 13.1323 17.839C13.1323 17.2177 12.6286 16.714 12.0073 16.714H11.9927Z\"\n                fill=\"#BE1025\"\/>\n            <\/svg>\n            Warning        <\/span>\n        <p class=\"announcement-block__content\">\n            Don't add <strong>NEXT_PUBLIC_<\/strong> to the beginning of these variable names, for example <strong>NEXT_PUBLIC_WORDPRESS_API_PASSWORD<\/strong>. If you do, Next.js includes the password in code sent to visitors' browsers, where anyone can inspect it and see the password.        <\/p>\n    <\/div>\n\n    \n\n\n\n<\/p><h2 class=\"wp-block-heading h-t-title-2\" id=\"h-7-add-seo-metadata-and-a-sitemap\">7. Add SEO metadata and a sitemap<\/h2><p class=\"wp-block-paragraph\">Add SEO metadata and a sitemap to your Next.js app by updating <strong>app\/posts\/[slug]\/page.js<\/strong> with a page title and meta description and creating <strong>app\/sitemap.js<\/strong> for your public URLs.<\/p><p class=\"wp-block-paragraph\">Open <strong>app\/posts\/[slug]\/page.js<\/strong> and find this line:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">`${process.env.WORDPRESS_API_URL}\/posts?slug=${encodeURIComponent(slug)}&amp;_fields=id,slug,title,content`,<\/pre><p class=\"wp-block-paragraph\">Replace it with:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">`${process.env.WORDPRESS_API_URL}\/posts?slug=${encodeURIComponent(slug)}&amp;_fields=id,slug,title,excerpt,content`,<\/pre><p class=\"wp-block-paragraph\">Next, find this line in the same file:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">export default async function PostPage({ params }) {<\/pre><p class=\"wp-block-paragraph\">Add the following code immediately above it:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">function stripHtml(html) {\n   return html\n      .replace(\/&lt;[^&gt;]*&gt;\/g, '')\n      .replace(\/s+\/g, ' ')\n      .trim();\n}\n\nexport async function generateMetadata({ params }) {\n   const { slug } = await params;\n   const post = await getPost(slug);\n\n   if (!post) {\n      return {};\n   }\n\n   return {\n      title: stripHtml(post.title.rendered),\n      description: stripHtml(\n         post.excerpt.rendered\n      ).slice(0, 160),\n   };\n}\n<\/pre><p class=\"wp-block-paragraph\">The <strong>generateMetadata()<\/strong> function uses the WordPress post title and excerpt as the page title and meta description.<\/p><p class=\"wp-block-paragraph\">Save <strong>app\/posts\/[slug]\/page.js<\/strong>, then reload one of your post URLs. The browser tab should now show the post title.<\/p><div class=\"wp-block-image\"><figure data-wp-context='{\"imageId\":\"6aac2062b26ff\"}' data-wp-interactive=\"core\/image\" data-wp-key=\"6aac2062b26ff\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2026\/09\/nextjs-dynamic-route-wordpress-post.png\/w=1024,h=1024,fit=scale-down\" alt=\"Browser showing an individual WordPress post on a Next.js dynamic route at localhost:3000 with the post slug in the URL.\" class=\"wp-image-157091\" title=\"nextjs-dynamic-route-wordpress-post\"><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" data-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on--click=\"actions.showLightbox\" data-wp-style--right=\"state.thisImage.buttonRight\" data-wp-style--top=\"state.thisImage.buttonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><p class=\"wp-block-paragraph\">To check the meta description, open the page source and search for <strong>description<\/strong>. You should see a <strong>meta<\/strong> tag containing the WordPress post excerpt.<\/p><p class=\"wp-block-paragraph\">Your home page still uses the placeholder title from the Next.js project. Open <strong>app\/layout.js<\/strong>, then replace the existing metadata block with your own:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">export const metadata = {\ntitle: 'Latest posts',\ndescription: 'Articles published on my WordPress site.',\n};<\/pre><p class=\"wp-block-paragraph\">Next, create the sitemap. Open <strong>.env.local<\/strong> and add the domain you plan to use for your Next.js app:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">SITE_URL=https:\/\/frontend.domain.tld<\/pre><p class=\"wp-block-paragraph\">Replace <strong>frontend.domain.tld<\/strong> with the domain you&rsquo;ll connect during deployment, then save the file.<\/p><p class=\"wp-block-paragraph\">Inside the <strong>app<\/strong> folder, create a file named <strong>sitemap.js<\/strong>, then add:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">async function getAllPostSlugs() {\n   const posts = [];\n   let page = 1;\n   let totalPages = 1;\n\n   do {\n      const response = await fetch(\n         `${process.env.WORDPRESS_API_URL}\/posts?per_page=100&amp;page=${page}&amp;_fields=slug`,\n         { cache: 'no-store' }\n      );\n\n      if (!response.ok) {\n         throw new Error(\n            `Failed to fetch posts: ${response.status}`\n         );\n      }\n\n      const currentPosts = await response.json();\n      posts.push(...currentPosts);\n\n      totalPages = Number(\n         response.headers.get('X-WP-TotalPages') || 1\n      );\n\n      page += 1;\n   } while (page &lt;= totalPages);\n\n   return posts;\n}\n\nexport default async function sitemap() {\n   const posts = await getAllPostSlugs();\n   const siteUrl = process.env.SITE_URL;\n\n   return [\n      {\n         url: siteUrl,\n      },\n      ...posts.map((post) =&gt; ({\n         url: `${siteUrl}\/posts\/${post.slug}`,\n      })),\n   ];\n}<\/pre><p class=\"wp-block-paragraph\">Save <strong>app\/sitemap.js <\/strong>when you&rsquo;re done.<\/p><p class=\"wp-block-paragraph\">Go to your terminal and press <strong>Ctrl+C<\/strong> to stop the server. Then restart it so Next.js loads <strong>SITE_URL<\/strong>:<\/p><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">npm run dev<\/pre><p class=\"wp-block-paragraph\">Open <strong>http:\/\/localhost:3000\/sitemap.xml<\/strong> in your browser. You&rsquo;ll see your home page and all published post URLs in the sitemap:<\/p><div class=\"wp-block-image\"><figure data-wp-context='{\"imageId\":\"6aac2062b9b73\"}' data-wp-interactive=\"core\/image\" data-wp-key=\"6aac2062b9b73\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2026\/09\/nextjs-sitemap-xml-post-urls.png\/w=1024,h=1024,fit=scale-down\" alt=\"Browser showing the generated sitemap.xml file with XML entries for the site home page and a published WordPress post URL.\" class=\"wp-image-157096\" title=\"nextjs-sitemap-xml-post-urls\"><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" data-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on--click=\"actions.showLightbox\" data-wp-style--right=\"state.thisImage.buttonRight\" data-wp-style--top=\"state.thisImage.buttonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><h2 class=\"wp-block-heading h-t-title-2\" id=\"h-8-deploy-the-next-js-app\">8. Deploy the Next.js app<\/h2><p class=\"wp-block-paragraph\">To deploy your Next.js app, use an IDE extension to publish your project directly from VS Code to your web app hosting provider.<\/p><p class=\"wp-block-paragraph\">For this example, we&rsquo;ll deploy the project to a Hostinger Web Apps plan using <a href=\"\/au\/connector\">Hostinger Connector<\/a>. It&rsquo;s free and works with various Integrated Development Environments (IDEs), including VS Code, Cursor, Devin, and Antigravity.<\/p><p class=\"wp-block-paragraph\">Here&rsquo;s <a href=\"\/au\/tutorials\/how-to-deploy-a-website-from-vs-code\/\">how to deploy from VS Code<\/a> using Hostinger Connector:<\/p><ol class=\"wp-block-list\">\n<li>Open the <strong>Extensions<\/strong> panel in VS Code, search for <strong>Hostinger Connector<\/strong>, then hit <strong>Install<\/strong>.<\/li>\n<\/ol><ol start=\"2\" class=\"wp-block-list\">\n<li>Click the <strong>Hostinger<\/strong> icon in the left sidebar, select <strong>1-Click Connect<\/strong>, then follow the onscreen instructions to connect your Hostinger account.<\/li>\n\n\n\n<li>Restart VS Code, then confirm that Hostinger Connector shows your account as connected.<\/li>\n<\/ol><div class=\"wp-block-image\"><figure data-wp-context='{\"imageId\":\"6aac2062beb8c\"}' data-wp-interactive=\"core\/image\" data-wp-key=\"6aac2062beb8c\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2026\/09\/vscode-hostinger-connector-connected-tools.png\/w=1024,h=1024,fit=scale-down\" alt=\"Hostinger Connector panel in Visual Studio Code showing a connected Hostinger account and enabled tools for websites, domains, subscriptions, and other services.\" class=\"wp-image-157097\" title=\"vscode-hostinger-connector-connected-tools\"><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" data-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on--click=\"actions.showLightbox\" data-wp-style--right=\"state.thisImage.buttonRight\" data-wp-style--top=\"state.thisImage.buttonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><ol start=\"4\" class=\"wp-block-list\">\n<li>Open the chat panel, then click <strong>Configure Tools<\/strong> at the bottom of the chat input.<\/li>\n\n\n\n<li>Confirm that the Hostinger tools are listed and enabled.<\/li>\n<\/ol><div class=\"wp-block-image\"><figure data-wp-context='{\"imageId\":\"6aac2062c5b87\"}' data-wp-interactive=\"core\/image\" data-wp-key=\"6aac2062c5b87\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2026\/07\/vs-code-configure-tools-hostinger-mcp-servers-highlighted.png\/w=1024,h=1024,fit=scale-down\" alt=\"VS Code Configure Tools dialog with Hostinger MCP Server tools enabled\" class=\"wp-image-152609\"><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" data-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on--click=\"actions.showLightbox\" data-wp-style--right=\"state.thisImage.buttonRight\" data-wp-style--top=\"state.thisImage.buttonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><ol start=\"6\" class=\"wp-block-list\">\n<li>Ask your agent to deploy the project. You can use a prompt like this:<\/li>\n<\/ol><pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Deploy this project to my Hostinger Web Apps plan and connect it to https:\/\/frontend.domain.tld.\nConfigure the following as environment variables:\n- WORDPRESS_API_URL=https:\/\/domain.tld\/wp-json\/wp\/v2\n- SITE_URL=https:\/\/frontend.domain.tld<\/pre><p class=\"wp-block-paragraph\">Replace the placeholders with your own domains. Also, add <strong>WORDPRESS_API_USERNAME<\/strong> and <strong>WORDPRESS_API_PASSWORD<\/strong> if your project requires authenticated WordPress requests.<\/p><p class=\"wp-block-paragraph\">Your agent will use Hostinger Connector to handle the deployment and show its progress in the chat. You should see a confirmation that the deployment succeeded and a live URL for your Next.js app after it finishes.<\/p><div class=\"wp-block-image\"><figure data-wp-context='{\"imageId\":\"6aac2062ca9ea\"}' data-wp-interactive=\"core\/image\" data-wp-key=\"6aac2062ca9ea\" class=\"aligncenter size-large wp-lightbox-container\"><img decoding=\"async\" data-wp-class--hide=\"state.isContentHidden\" data-wp-class--show=\"state.isContentVisible\" data-wp-init=\"callbacks.setButtonStyles\" data-wp-on--click=\"actions.showLightbox\" data-wp-on--load=\"callbacks.setButtonStyles\" data-wp-on--pointerdown=\"actions.preloadImage\" data-wp-on--pointerenter=\"actions.preloadImageWithDelay\" data-wp-on--pointerleave=\"actions.cancelPreload\" data-wp-on-window--resize=\"callbacks.setButtonStyles\" src=\"https:\/\/imagedelivery.net\/LqiWLm-3MGbYHtFuUbcBtA\/wp-content\/uploads\/sites\/2\/2026\/09\/hostinger-web-apps-nextjs-deployment-complete.png\/w=1024,h=1024,fit=scale-down\" alt=\"Visual Studio Code chat showing a completed Next.js deployment to Hostinger Web Apps with the project name, target URL, environment variables, and live deployment status.\" class=\"wp-image-157089\" title=\"hostinger-web-apps-nextjs-deployment-complete\"><button class=\"lightbox-trigger\" type=\"button\" aria-haspopup=\"dialog\" data-wp-bind--aria-label=\"state.thisImage.triggerButtonAriaLabel\" data-wp-init=\"callbacks.initTriggerButton\" data-wp-on--click=\"actions.showLightbox\" data-wp-style--right=\"state.thisImage.buttonRight\" data-wp-style--top=\"state.thisImage.buttonTop\">\n\t\t\t<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"12\" height=\"12\" fill=\"none\" viewbox=\"0 0 12 12\">\n\t\t\t\t<path fill=\"#fff\" d=\"M2 0a2 2 0 0 0-2 2v2h1.5V2a.5.5 0 0 1 .5-.5h2V0H2Zm2 10.5H2a.5.5 0 0 1-.5-.5V8H0v2a2 2 0 0 0 2 2h2v-1.5ZM8 12v-1.5h2a.5.5 0 0 0 .5-.5V8H12v2a2 2 0 0 1-2 2H8Zm2-12a2 2 0 0 1 2 2v2h-1.5V2a.5.5 0 0 0-.5-.5H8V0h2Z\"><\/path>\n\t\t\t<\/svg>\n\t\t<\/button><\/figure><\/div><p class=\"wp-block-paragraph\">Your Next.js app is now live. Test its connection to WordPress by publishing a new post with a featured image, then refresh your site and confirm that the post appears and its individual page opens correctly.<\/p><p>        <div class=\"protip\">\n            <div class=\"protip__heading\">\n                <svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">\n                    <path d=\"M1.49234 23.5024C1.23229 23.5024 0.972242 23.4024 0.782206 23.2123C0.562165 22.9923 0.452144 22.6822 0.502153 22.3722C0.562165 21.9221 1.14227 17.9113 3.00262 16.351C3.63274 15.8209 4.43289 15.5509 5.26305 15.5609C6.09321 15.5909 6.87335 15.9109 7.47347 16.4911C8.6937 17.6913 8.76371 19.6717 7.6435 20.9919C6.0832 22.8523 2.08245 23.4324 1.63237 23.4924C1.59236 23.4924 1.54235 23.4924 1.50234 23.4924L1.49234 23.5024ZM5.16303 17.5613C4.84297 17.5613 4.53291 17.6713 4.29287 17.8813C3.60274 18.4614 3.07264 19.9317 2.75258 21.242C4.06282 20.9219 5.5331 20.3918 6.11321 19.7017C6.55329 19.1716 6.54329 18.3814 6.0832 17.9213C5.85316 17.7013 5.5431 17.5713 5.20304 17.5613C5.19304 17.5613 5.17303 17.5613 5.16303 17.5613ZM11.7243 21.8821C11.4942 21.8821 11.2642 21.8021 11.0841 21.652C10.8541 21.462 10.7241 21.1819 10.7241 20.8819V15.9109L8.08358 13.2705H3.11264C2.81259 13.2705 2.53254 13.1404 2.3425 12.9104C2.15246 12.6803 2.07245 12.3803 2.12246 12.0902C2.19247 11.7102 2.84259 8.36953 4.70294 7.12929C6.33325 6.04909 8.96375 6.49918 10.244 6.80923C11.5442 4.96889 13.2546 3.4286 15.2349 2.33839C17.4553 1.11816 19.9858 0.518051 22.4963 0.498047C23.0464 0.498047 23.4865 0.948132 23.4865 1.49824C23.4865 5.0389 22.3763 9.97983 17.1753 13.7605C17.4853 15.0408 17.9354 17.6613 16.8552 19.2816C15.615 21.1419 12.2744 21.7921 11.8943 21.8621C11.8343 21.8721 11.7743 21.8821 11.7143 21.8821H11.7243ZM12.7245 16.181V19.6016C13.7146 19.2916 14.7948 18.7915 15.2049 18.1814C15.675 17.4812 15.605 16.091 15.385 14.9008C14.5248 15.3808 13.6346 15.8109 12.7245 16.181ZM9.66388 12.0302L11.9643 14.3307C13.1845 13.8306 14.3648 13.2204 15.485 12.5103C19.9358 9.51974 21.2361 5.60901 21.4561 2.53843C19.6157 2.67846 17.8254 3.20856 16.2051 4.09872C14.2847 5.14892 12.6544 6.68921 11.4942 8.54956C10.7841 9.65977 10.174 10.82 9.66388 12.0302ZM4.39289 11.2701H7.81353C8.1936 10.3599 8.63368 9.46974 9.11377 8.60957C7.92355 8.38953 6.51329 8.31952 5.81315 8.78961C5.19304 9.19968 4.70294 10.3099 4.39289 11.2701Z\" fill=\"#673DE6\"\/>\n                <\/svg>\n                <p class=\"protip__title\">\n                    Troubleshoot deployment issues                <\/p>\n            <\/div>\n            <p class=\"protip__content\"> You can ask your agent to check your deployment if the site doesn&rsquo;t load or your WordPress content doesn&rsquo;t appear. For example:<\/p><p><strong>Show the latest deployment logs for this project and identify why my app isn't loading correctly.<\/strong><\/p><p>Once you&rsquo;ve fixed the issue, ask the agent to redeploy the project to the same Web Apps plan. <\/p>\n                    <\/div>\n        \n\n\n\n<\/p><p class=\"wp-block-paragraph\">Alternatively, push your project to a GitHub repository, then connect that repository to your Web Apps hosting plan and let Hostinger take it from there.<\/p><p class=\"wp-block-paragraph\">This way, whenever you make changes to your code and push them to the repository, Hostinger will automatically redeploy your app. The process is similar when you <a href=\"\/au\/tutorials\/how-to-deploy-a-website-from-your-ide\/\">deploy using other IDEs<\/a>.<\/p><h2 class=\"wp-block-heading h-t-title-2\" id=\"h-when-to-choose-react-with-wordpress\">When to choose React with WordPress<\/h2><p class=\"wp-block-paragraph\">Choose React with WordPress when <strong>you want to keep WordPress for content management but need a separate front end for more control, interactivity, or publishing the same content across multiple platforms<\/strong>.<\/p><p class=\"wp-block-paragraph\">This WordPress-React setup works well when:<\/p><ul class=\"wp-block-list\">\n<li><strong>Your site needs complex interactive features.<\/strong> React works well for advanced filters, dashboards, and account interfaces.<\/li>\n\n\n\n<li><strong>You need the same content in several places.<\/strong> The WordPress REST API can supply content to your website, mobile app, or another front end.<\/li>\n\n\n\n<li><strong>You need more control over the front end.<\/strong> A separate Next.js app lets you manage its routing, rendering, caching, and interface independently from WordPress.<\/li>\n<\/ul><p class=\"wp-block-paragraph\">Stick with a traditional WordPress setup when a separate React app would add more work than value:<\/p><ul class=\"wp-block-list\">\n<li><strong>Your site depends heavily on plugins that control the front end.<\/strong> Page builders, shortcodes, membership features, and some WooCommerce functionality need extra work when you replace the WordPress theme.<\/li>\n\n\n\n<li><strong>You don&rsquo;t want to maintain a separate JavaScript app.<\/strong> A headless setup adds another project, deployment process, routing system, and hosting environment to manage.<\/li>\n\n\n\n<li><strong>Your site is mainly a simple blog or content website.<\/strong> A WordPress theme already handles templates, URLs, SEO output, previews, and plugin compatibility without a separate front end.<\/li>\n<\/ul><h2 class=\"wp-block-heading h-t-title-2\" id=\"h-next-steps-for-your-react-and-wordpress-project\">Next steps for your React and WordPress project<\/h2><p class=\"wp-block-paragraph\">The next steps for your React and WordPress project include <strong>adding blog pagination for easier navigation, building an ecommerce front end with WooCommerce, adding Draft Mode to preview unpublished posts, and connecting other data sources<\/strong>.<\/p><ul class=\"wp-block-list\">\n<li><strong>Add blog pagination.<\/strong> Limit the number of posts shown in your post list, then add navigation so visitors can move between pages. Use the WordPress REST API&rsquo;s <strong>page<\/strong> and <strong>per_page<\/strong> parameters and the <strong>X-WP-TotalPages<\/strong> response header to build the navigation.<\/li>\n\n\n\n<li><strong>Build an ecommerce front end.<\/strong> Keep WooCommerce in WordPress to manage products and orders while Next.js handles the storefront. Use the WooCommerce APIs to bring product, cart, checkout, and customer data into the parts of your front end that need them.<\/li>\n\n\n\n<li><strong>Add draft previews.<\/strong> Preview unpublished WordPress posts in the same Next.js layout they&rsquo;ll use after publishing. Use Next.js Draft Mode with an authenticated WordPress request, then open the post&rsquo;s existing Next.js URL to review it before publishing.<\/li>\n\n\n\n<li><strong>Connect other data sources.<\/strong> Add another API when your pages need information that WordPress doesn&rsquo;t store, such as inventory, search results, account data, or application records. Request that data separately, then combine it with your WordPress content where you need it.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>To use React with WordPress, keep WordPress as your content management system (CMS), then use its REST API to fetch and display your content in a separate React front end. In this WordPress-React setup, you build the front end on your computer using Next.js as the React framework, then deploy it to your web app [&#8230;]<\/p>\n<p><a class=\"btn btn-secondary understrap-read-more-link\" href=\"\/au\/tutorials\/wordpress-react\/\">Read More&#8230;<\/a><\/p>\n","protected":false},"author":411,"featured_media":149431,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"rank_math_title":"How to use React with WordPress (%currentyear% guide)","rank_math_description":"Learn how to use React with WordPress by building a Next.js front end, fetching content through the REST API, and deploying the finished app.","rank_math_focus_keyword":"wordpress react","footnotes":""},"categories":[22673],"tags":[],"class_list":["post-48798","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-wordpress"],"hreflangs":[{"locale":"en-US","link":"https:\/\/www.hostinger.com\/tutorials\/wordpress-react","default":0},{"locale":"pt-BR","link":"https:\/\/www.hostinger.com\/br\/tutoriais\/wordpress-react\/","default":0},{"locale":"fr-FR","link":"https:\/\/www.hostinger.com\/fr\/tutoriels\/react-wordpress\/","default":0},{"locale":"es-ES","link":"https:\/\/www.hostinger.com\/es\/tutoriales\/wordpress-react\/","default":0},{"locale":"id-ID","link":"https:\/\/www.hostinger.com\/id\/tutorial\/panduan-wordpress-react\/","default":0},{"locale":"en-PH","link":"https:\/\/www.hostinger.com\/ph\/tutorials\/wordpress-react","default":0},{"locale":"en-MY","link":"https:\/\/www.hostinger.com\/my\/tutorials\/wordpress-react","default":0},{"locale":"en-GB","link":"https:\/\/www.hostinger.com\/uk\/tutorials\/wordpress-react","default":0},{"locale":"en-IN","link":"https:\/\/www.hostinger.com\/in\/tutorials\/wordpress-react","default":0},{"locale":"en-CA","link":"https:\/\/www.hostinger.com\/ca\/tutorials\/wordpress-react","default":0},{"locale":"en-AU","link":"https:\/\/www.hostinger.com\/au\/tutorials\/wordpress-react","default":0},{"locale":"en-NG","link":"https:\/\/www.hostinger.com\/ng\/tutorials\/wordpress-react","default":0},{"locale":"es-AR","link":"https:\/\/www.hostinger.com\/ar\/tutoriales\/wordpress-react\/","default":0},{"locale":"es-MX","link":"https:\/\/www.hostinger.com\/mx\/tutoriales\/wordpress-react\/","default":0},{"locale":"es-CO","link":"https:\/\/www.hostinger.com\/co\/tutoriales\/wordpress-react\/","default":0},{"locale":"pt-PT","link":"https:\/\/www.hostinger.com\/pt\/tutoriais\/wordpress-react\/","default":0}],"_links":{"self":[{"href":"https:\/\/www.hostinger.com\/au\/tutorials\/wp-json\/wp\/v2\/posts\/48798","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.hostinger.com\/au\/tutorials\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.hostinger.com\/au\/tutorials\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.hostinger.com\/au\/tutorials\/wp-json\/wp\/v2\/users\/411"}],"replies":[{"embeddable":true,"href":"https:\/\/www.hostinger.com\/au\/tutorials\/wp-json\/wp\/v2\/comments?post=48798"}],"version-history":[{"count":29,"href":"https:\/\/www.hostinger.com\/au\/tutorials\/wp-json\/wp\/v2\/posts\/48798\/revisions"}],"predecessor-version":[{"id":149430,"href":"https:\/\/www.hostinger.com\/au\/tutorials\/wp-json\/wp\/v2\/posts\/48798\/revisions\/149430"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.hostinger.com\/au\/tutorials\/wp-json\/wp\/v2\/media\/149431"}],"wp:attachment":[{"href":"https:\/\/www.hostinger.com\/au\/tutorials\/wp-json\/wp\/v2\/media?parent=48798"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.hostinger.com\/au\/tutorials\/wp-json\/wp\/v2\/categories?post=48798"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.hostinger.com\/au\/tutorials\/wp-json\/wp\/v2\/tags?post=48798"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}