{"id":17445662,"url":"https://github.com/ziad-saab/wordpress-api-nextjs-theme","last_synced_at":"2025-04-19T13:48:37.484Z","repository":{"id":147654070,"uuid":"110471272","full_name":"ziad-saab/wordpress-api-nextjs-theme","owner":"ziad-saab","description":"A workshop on creating a WordPress theme with React and Next.js for WordCamp Montreal","archived":false,"fork":false,"pushed_at":"2019-01-16T14:42:37.000Z","size":676,"stargazers_count":45,"open_issues_count":2,"forks_count":6,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-29T08:22:48.846Z","etag":null,"topics":["nextjs","nodejs","react","seo","ssr","wordpress"],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ziad-saab.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-11-12T21:37:00.000Z","updated_at":"2024-08-15T09:17:36.000Z","dependencies_parsed_at":null,"dependency_job_id":"cd848a53-db38-4e3a-b4e6-af24bbc0506b","html_url":"https://github.com/ziad-saab/wordpress-api-nextjs-theme","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziad-saab%2Fwordpress-api-nextjs-theme","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziad-saab%2Fwordpress-api-nextjs-theme/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziad-saab%2Fwordpress-api-nextjs-theme/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ziad-saab%2Fwordpress-api-nextjs-theme/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ziad-saab","download_url":"https://codeload.github.com/ziad-saab/wordpress-api-nextjs-theme/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249707025,"owners_count":21313750,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["nextjs","nodejs","react","seo","ssr","wordpress"],"created_at":"2024-10-17T18:06:38.575Z","updated_at":"2025-04-19T13:48:37.454Z","avatar_url":"https://github.com/ziad-saab.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Let's make a WordPress-based blog with React and NodeJS\n\nIn this workshop, we are going to make a blog. To manage the content of our blog, we will be using\nWordPress, one of the leading content management systems today. However, contrary to most WordPress\nblogs in existence today, we will not be using PHP to create the theme for our blog. Instead, we are\ngoing to manage the front-end with a variety of modern web technologies: JavaScript, React, and some\nNodeJS.\n\n## Why choose this tech stack? :question:\nIt's important to note that this way of doing things is not for everyone. As always, our\ntechnology choices will have some upsides and some downsides. In the case of choosing React, here\nare, in no particular order, a few pros and cons:\n\n* :fire: Pros:\n    * Extremely rapid feedback during the development cycle\n    * The resulting single-page application can have faster loading times and navigation\n    * Great ability and ease to add interactive features like infinite scrolling\n    * Ability to get going without having to learn PHP\n    * Even greater separation between back-end and front-end\n    * Ease of managing different development environments without copying WordPress data\n* :poop: Cons:\n    * Some complexity will be introduced by having two different technologies – PHP and JavaScript\n    * Having a build process for the front-end complicates things\n    * The need to have two hosting environments, one for WordPress and one for our “theme”\n    * The reduced number of people who can help us due to our non-conventional setup\n\nNow that that's out of the way, let's not come back to it and concentrate on the task at hand.\n\n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://media0.giphy.com/media/l4EoT59vRYdTSi6vS/giphy.gif\"\u003e\n\u003c/p\u003e\n\n---\n\n## Technical requirements :computer:\n\n\u003cimg align=\"left\" height=\"200\" src=\"https://media0.giphy.com/media/wiAp4dCDPfm7u/giphy.gif\"\u003e\n\nIn order to successfully complete this workshop, you will need:\n\n* A computer with an internet connection\n* The software mentioned in the [next section](#required-software)\n* Some familiarity with modern JavaScript, React and NodeJS\n* Some familiarity with the command line interface of your machine\n* Familiarity with Git is optional, but you should use it to track your progress!\n\n---\n\n## Thanks :two_hearts:\nBefore moving forward with the workshop, I'd like to express my thanks to the people who\nhelped make this possible:\n\n- [Nathaniel Kitzke](https://github.com/nk1tz): for TAing and fixing a thousand typos\n- [Timothy Mulqueen](https://github.com/multimo): for TAing and fixing some glaring mistakes\n- [Paule Lepage](https://github.com/nyanofthemoon): for TAing and moral support\n\n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://media2.giphy.com/media/osjgQPWRx3cac/giphy.gif\"\u003e\n\u003c/p\u003e\n\n---\n\n## Getting started! :rocket:\n\n### Required software\nTo follow along successfully, you will need to install some software on your machine:\n\n* [Install NVM](https://github.com/creationix/nvm)\n\n  NVM is “Node Version Manager”. Through it, we can install any version of NodeJS. For this\n  workshop, we will be using **NodeJS 8.9.0**. Once NVM is installed, you can run the following\n  command to install NodeJS:\n  \n  ```sh\n  nvm install 8.9.0\n  nvm alias default 8.9.0\n  ```\n  \n  After running these commands, you might need to open a new shell for them to take effect.\n\n* [Install a recent version of Yarn](https://yarnpkg.com/lang/en/docs/install/)\n  \n  Yarn is a package manager for JavaScript code. It will allow us to install external libraries\n  easily. Yarn will also allow us to execute our code with simple command lines.\n\n* [Install the WebStorm IDE](https://www.jetbrains.com/webstorm/)\n\n  Many IDEs exist that support JavaScript. WebStorm is a phenomenal IDE that comes batteries\n  included. Once installed, you will not need any special configurations nor plugins. It will work\n  out of the box.\n  \n  **NOTE**: It is fine to use the IDE of your choice, but WebStorm is *recommended*. It will ensure\n  that you have the same setup as your workshop-mates and make it easier for the TAs.\n\n* [Install Google Chrome](https://www.google.com/chrome/index.html)\n\n  You can use the browser of your choice to follow this workshop, but Chrome is recommended. This\n  will ensure that you have the same setup as your workshop-mates and make it easier for the TAs.\n  \n* Install React Developer Tools\n\n  Available for [Chrome](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en)\n  and [Firefox](https://addons.mozilla.org/en-US/firefox/addon/react-devtools/), React Developer\n  Tools is a browser extension that allows you to introspect your running React application, making\n  it easier to debug.\n\n* **What about WordPress??** :question:\n\n  You will not need to install a WordPress instance in order to complete the workshop. If you\n  already have your WordPress blog with WP-API, you will simply be able to use it. If you do not\n  have a readily-available WordPress blog, you will be able to use [wired.com](https://wired.com)'s\n  WP-API.\n  \n  One of the great things about the setup we will have is that you will easily be able to change\n  which WordPress blog it is associated to by simply changing one line in your JavaScript code.\n  \n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://media2.giphy.com/media/yhcqymRLlv7K8/giphy.gif\"\u003e\n\u003c/p\u003e\n\n### Initial setup\nNow that you have installed all the necessary software on your machine, you are ready to start! In\nthis section, you will be initializing your development environment, creating the base code, and\nmaking your first web page with this setup.\n\nStart by creating an empty directory on your machine. Then, move to that directory and run the\nfollowing commands:\n\n```sh\nyarn init # You can edit your answers to the prompts later\nyarn add react react-dom next\n```\n\nBased on the requirements of this workshop, this should all be familiar to you except perhaps for\nthe `next` framework. [Next.js](https://github.com/zeit/next.js/) is the framework that will allow\nus to run the same React application on both the server (Express) and the client (browser). This is\nwhat will allow us to develop our blog with modern front-end technologies without having to forego\nthe benefits of an initial server rendering – SEO and faster loading.\n\nOnce this is done, open WebStorm and create a new project based on the directory you just setup.\nThen, open the `package.json` file and add the following section at the root:\n\n```\n  \"scripts\": {\n    \"start\": \"next\"\n  }\n```\n\nThen, create a directory called `pages` at the root of your project. In it, create a file called\n`index.js` with the following code:\n\n```js\nimport React from 'react';\n\nconst HomePage = () =\u003e (\n  \u003cdiv\u003e\n    \u003ch1\u003eWelcome to my blog\u003c/h1\u003e\n  \u003c/div\u003e\n);\n\nexport default HomePage;\n```\n\nOnce this is done, you will have created a basic Next.js setup. Go back to your command line and\nrun:\n\n```sh\nyarn start\n```\n\nAfter a few seconds, your app should be started. Open your browser and navigate to `localhost:3000`\nto see your new creation. If you “view source”, you will notice that the server sent the content of\nyour React component along with the response. If you open your React Developer Tools, you will see\nthat the React application is running in the browser. Two environments, one code! :fire:\n\nThis concludes the initial setup of the workshop. Let's seal the deal by\ncreating a Git repository and committing all this goodness.\n\nTo prevent Git from tracking undesirable artifact files, create a `.gitignore` file at the root of\nyour project with the following lines:\n\n```\n.next\nnode_modules\n```\n\nThen, run `git init`, `git add .` and `git commit` to seal your work inside the repository.\n\n:warning: **NOTE**: from now on, the workshop instructions will not mention Git anymore. It's up to\nyou if you want to use it to have a clean commit for each step of the workshop. If you do, it will\nallow you to look at the history of your code and afford you all the benefits Git has to offer. \n\n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://media.giphy.com/media/3ov9jJGk03x9MaxHB6/giphy.gif\"\u003e\n\u003c/p\u003e\n\n---\n\n## Adding another page and linking to it :link:\nNext.js makes it easy to add new pages to your application. All you have to do is create a new file\nunder the `pages` directory. The name of the file will be the same as the URL path to access it.\nWhile this seems limiting – how do we do pretty URLs?? – we will see in a later section that this\ncan be fixed by using Next.js' powerful request handler.\n\nCreate a new file called `blog.js` in the `pages` directory. In there, follow the same pattern as\n`index.js` to create a page that simply says “Recent blog posts”. In the next section, we will use\nthis page to call the WordPress API and create a blog listing.\n\nThen, re-open `pages/index.js` and make the following changes:\n\n```js\n// Add this line at the top of the file\nimport Link from 'next/link';\n\n// Add this line inside the \u003cdiv\u003e, below the \u003ch1\u003e\n\u003cLink href=\"/blog\"\u003e\u003ca\u003eGo to the recent posts\u003c/a\u003e\u003c/Link\u003e\n```\n\nThen, go back to your browser. Did you notice that the changes appear right away, without having to\nreload the page? This is one of the many benefits of using this tech stack, and with Next.js you get\nit for free. If you click on the link, you should navigate to the `/blog` page. Using your favorite\ntechnique, convince yourself that this navigation has happened without a full page reload.\n\n### Why not simply use an `\u003ca\u003e` for navigation?\nIf you look at the source code of the `/` page, you will see that the server has rendered an `\u003ca\u003e`\nin place of the `\u003cLink\u003e`. This is great for SEO purposes. However, using the `\u003cLink\u003e` component\nmakes this link dynamic. In the browser, Next.js prevents the default action and handles navigation\nusing the History API. This gives you the best of both worlds: SEO and :fire: fast navigation.\n\n:warning: **NOTE**: It might look weird to have an `\u003ca\u003e` *inside* the `\u003cLink\u003e` but this is how Next\nwants you to do it.\n\n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://media0.giphy.com/media/YWUpVw86AtIbe/giphy.gif\"\u003e\n\u003c/p\u003e\n\n---\n\n## First contact: using the WordPress API! :alien:\nIn this section, we will use the WordPress API to show a list of recent posts on the `/blog` page.\nThe WordPress API is based on the [application/hal+json](http://stateless.co/hal_specification.html).\nThis will make it easy for us to grab not only post data, but all sorts of related data like tags,\ncategories, and author info. This data is provided through the use of\n[hyperlinks](https://developer.wordpress.org/rest-api/using-the-rest-api/linking-and-embedding/),\nbut the WordPress API allows us to embed those links in order to avoid multiple HTTP requests.\n\nThe **TL;DR** of this is that WordPress API will include HTTP links to related resources, but adding\n`?_embed` at the end of a WordPress API URL will automatically embed all the linked resources. Here\nis an example of requesting a single blog post without `_embed`:\n\n```json\n{\n    \"id\": 2267743,\n    \"date\": \"2017-11-11T10:00:23\",\n    \"date_gmt\": \"2017-11-11T15:00:23\",\n    \"guid\": {\n        \"rendered\": \"https://www.wired.com/?p=2267743\"\n    },\n    \"modified\": \"2017-11-10T18:37:44\",\n    \"modified_gmt\": \"2017-11-10T23:37:44\",\n    \"slug\": \"review-incase-noviconnected-travel-roller\",\n    \"type\": \"post\",\n    \"link\": \"https://www.wired.com/2017/11/review-incase-noviconnected-travel-roller/\",\n    \"title\": {\n        \"rendered\": \"Review: Incase NoviConnected Travel Roller\"\n    },\n    \"content\": {\n        \"rendered\": \"\u003cp\u003eIf you’re the kind of person who will arrive at the airport without charging your phone or laptop, you are also the kind of person who will forget to charge the battery bank in your smart luggage. I discovered this when a three-hour delay kicked off my weekend trip with Incase’s NoviConnected carry-on suitcase. Sorry, but it’s true.\u003c/p\u003e\\n\u003cp\u003eAt home, it took six hours of wall charging at home to get the NoviConnected’s 10,050-mAh battery up to 100%. A few days later, I packed my bag, headed to the airport and waited at the gate. By the time I realized I needed to charge my phone, the suitcase’s battery had gone down to 76% after doing nothing, with an estimated one hour and 23 minutes’ worth of juice. \u003c/p\u003e\\n\u003cp\u003eCharging my phone back up to 80% took the suitcase’s battery down to 19%. What about charging a laptop? Forget about it. The bag’s battery bank is strictly for small devices only and it\u0026#8217;s best conserved for an emergency situation. Since everything from your boarding pass to your hotel reservation is on your phone, you\u0026#8217;ll need that to be juiced up, so it\u0026#8217;s not a total loss. Like the rest of the bag’s smart features, it may promise more than it can actually deliver.\u003c/p\u003e\\n\u003cp\u003eFor example, take the suitcase’s Smart Luggage tracker. It uses Bluetooth and has a range of up to 33 feet. On a full flight, I ended up checking my carry-on at the airline’s request. When I got to baggage claim, I glanced at the Incase app to determine my luggage’s whereabouts and found out that my bag was out of range. For fifteen minutes, I waited at the baggage carousel like an ordinary plebe, trapped in a seemingly endless purgatory, mired in deep existential uncertainty. \u003c/p\u003e\\n\u003cp\u003eEven walking across my house prompted a notification from my phone, alerting me that the suitcase\u0026#8212;safely in my office\u0026#8212;was no longer within range. If you want a smart luggage tracker, it should probably use GPS instead of Bluetooth.\u003c/p\u003e\\n\\n\u003cdiv id=\\\"wired-tired\\\" class=\\\"carve col sm-col-18 med-col-9 big-col-9 fader proxima gray-5 relative\\\" data-share data-js=\\\"fader\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cdiv id=\\\"wired-tired-text\\\" class=\\\"pad-t-med\\\"\u003e\\n\\t\\t\u003ch2 class=\\\"tungsten no-marg\\\"\u003eIncase NoviConnected Travel Roller\u003c/h2\u003e\\n\\t\\t\u003ch3 class=\\\"tungsten gray-5 no-marg\\\"\u003e5/10\u003c/h3\u003e\\t\\t\u003cdiv class=\\\"border-box border-t hide-mob hide-sm hide-med\\\"\u003e\\n\\t\\t\\t\u003ca class=\\\"byline gray-5 no-underline no-hover\\\" href=\\\"#\\\" data-ui=\\\"howRateOpen\\\"\u003eLearn How We Rate\u003c/a\u003e\\n\\t\\t\u003c/div\u003e\\n\\t\\t\u003ch5 class=\\\"brandon uppercase border-t\\\"\u003eWired\u003c/h5\u003e\\n\\t\\t\u003cp class=\\\"gray-5\\\"\u003eBeautiful. Durable. Hubless wheels roll smoothly and easily and are easily replaceable. Battery bank will save you in a pinch. Comes with rain cover.\u003c/p\u003e\\n\\t\\t\u003ch5 class=\\\"brandon uppercase border-t\\\"\u003eTired\u003c/h5\u003e\\n\\t\\t\u003cp class=\\\"gray-5\\\"\u003eBluetooth tracker makes no sense. No side handle. Battery life was disappointing.\u003c/p\u003e\\n\\t\\t\u003cdiv class=\\\"border-box border-t border-b gray-5\\\"\u003e\\n\\t\\t\\t\\t\\t\\t\\t\\t\u003ca class=\\\"byline float-l no-underline no-hover\\\" rel=\\\"nofollow\\\" href=\\\"https://www.amazon.com/dp/B076B6LW8T/?tag=w050b-20\\\" target=\\\"_blank\\\"\u003eBuy It Now\u003c/a\u003e\\n\\t\\t\\t\u003cspan class=\\\"byline gray-5\\\"\u003e\u0026nbsp;\u0026nbsp;|\u0026nbsp;\u0026nbsp;Incase\u003c/span\u003e\\n\\t\\t\\t\\t\\t\\t\\t\u003c/div\u003e\\n\\t\u003c/div\u003e\\n\\n\\t\u003cdiv id=\\\"how-rate\\\" class=\\\"hide hide-mob hide-sm hide-med bg-white absolute bottom left right\\\" data-js='howWeRate'\u003e\\n\\t\\t\u003ca data-ui=\\\"howRateClose\\\" class=\\\"float-r no-underline no-hover marg-t-med\\\"\u003e\\n\\t\\t\\t\u003ci aria-hidden=\\\"true\\\" role=\\\"presentation\\\" class=\\\"ui ui-close opacity-3 r-align\\\"\u003e\u003c/i\u003e\\n\\t\\t\u003c/a\u003e\\n\\t\\t\u003ch4 class=\\\"tungsten clamp-6 pad-t-med pad-b-med clearfix\\\"\u003eHow We Rate\u003c/h4\u003e\\n\\t\\t\u003cdiv class=\\\"border-box border-t border-b pad-t-med pad-b-med\\\"\u003e\\n\\t\\t\\t\u003cul class=\\\"score-key list-none no-marg\\\"\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e1/10\u003c/span\u003eA complete failure in every way\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e2/10\u003c/span\u003eSad, really\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e3/10\u003c/span\u003eSerious flaws; proceed with caution\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e4/10\u003c/span\u003eDownsides outweigh upsides\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e5/10\u003c/span\u003eRecommended with reservations\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e6/10\u003c/span\u003eSolid with some issues\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e7/10\u003c/span\u003eVery good, but not quite great\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e8/10\u003c/span\u003eExcellent, with room to kvetch\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e9/10\u003c/span\u003eNearly flawless\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e10/10\u003c/span\u003eMetaphysical perfection\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\u003c/ul\u003e\\n\\t\\t\u003c/div\u003e\\n\\t\u003c/div\u003e\\n\u003c/div\u003e\\n\\n\u003cp\u003eBut as a piece of ordinary luggage, the bag performed admirably. It has a simple, durable makrolon polycarbonate shell that showed little wear after a few trips around the baggage carousel. The navy color of my sample case was elegant and surprisingly distinctive among a billion black bags. The interior, with mesh zip pockets and an included laundry bag, easily accommodated several days’ worth of clothes and shoes. \u003c/p\u003e\\n\u003cp\u003eThe bag was impossible to tip over, even with a tote bag hooked on top. The hubless wheels rolled smoothly—almost a little too smoothly, as the suitcase escaped me when my attention wandered on a slanting parking lot garage floor—and it’s simple to pop the wheels on and off if you need to fit the suitcase into an overhead compartment. This model also carries a lifetime warranty from Incase, so the wheels are covered if they break (the power bank\u0026#8217;s embedded battery has a two-year limited warranty). \u003c/p\u003e\\n\u003cp\u003eThe integrated TSA-approved lock works like you\u0026#8217;d expect, and it proved useful when I ended up checking my carry-on to save overhead space on two packed flights. However, I would have appreciated a side handle to make it easier to hoist around. \u003c/p\u003e\\n\u003cp\u003eAs holiday travel season approaches, you do have to find a way to get all your stuff from Point A to Point B without carrying it all in your arms. Incase is known for their attractive, Apple-compatible accessories and bags, and the NoviConnected is no exception. It’s elegant, rolls with ease, and has the capacity to save your tuchus right when you need it most—when your phone is about to die and you have no way to text your ride or check your car rental confirmation number.\u003c/p\u003e\\n\u003cp\u003eBut there are just too many other moderately-priced smart carry-ons out there right now with features that make a bit more sense, like a true GPS locator that can tell you if your baggage has made it to your destination. Or just bring your own \u003ca rel=\\\"nofollow\\\" href=\\\"https://www.amazon.com/dp/B0194WDVHI/?tag=w050b-20\\\" target=\\\"_blank\\\"\u003espare battery\u003c/a\u003e and pop a \u003ca rel=\\\"nofollow\\\" href=\\\"https://www.amazon.com/dp/B01MF9VQOP/?tag=w050b-20\\\" target=\\\"_blank\\\"\u003eTile tracker\u003c/a\u003e into your current luggage and call it a day.\u003c/p\u003e\\n\"\n    },\n    \"excerpt\": {\n        \"rendered\": \"\u003cp\u003eA great piece of luggage is brought down by its poor smart features.\u003c/p\u003e\\n\"\n    },\n    \"author\": 609,\n    \"featured_media\": 2267773,\n    \"comment_status\": \"open\",\n    \"ping_status\": \"closed\",\n    \"sticky\": false,\n    \"format\": \"standard\",\n    \"categories\": [\n        36,\n        83082,\n        4\n    ],\n    \"tags\": [\n        106059,\n        106060\n    ],\n    \"series\": [],\n    \"admin-settings\": [],\n    \"_links\": {\n        \"self\": [\n            {\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/posts/2267743\"\n            }\n        ],\n        \"collection\": [\n            {\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/posts\"\n            }\n        ],\n        \"about\": [\n            {\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/types/post\"\n            }\n        ],\n        \"author\": [\n            {\n                \"embeddable\": true,\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/users/609\"\n            }\n        ],\n        \"replies\": [\n            {\n                \"embeddable\": true,\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/comments?post=2267743\"\n            }\n        ],\n        \"version-history\": [\n            {\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/posts/2267743/revisions\"\n            }\n        ],\n        \"wp:featuredmedia\": [\n            {\n                \"embeddable\": true,\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/media/2267773\"\n            }\n        ],\n        \"wp:attachment\": [\n            {\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/media?parent=2267743\"\n            }\n        ],\n        \"wp:term\": [\n            {\n                \"taxonomy\": \"category\",\n                \"embeddable\": true,\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/categories?post=2267743\"\n            },\n            {\n                \"taxonomy\": \"post_tag\",\n                \"embeddable\": true,\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/tags?post=2267743\"\n            },\n            {\n                \"taxonomy\": \"series\",\n                \"embeddable\": true,\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/series?post=2267743\"\n            },\n            {\n                \"taxonomy\": \"admin-settings\",\n                \"embeddable\": true,\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/admin-settings?post=2267743\"\n            }\n        ],\n        \"curies\": [\n            {\n                \"name\": \"wp\",\n                \"href\": \"https://api.w.org/{rel}\",\n                \"templated\": true\n            }\n        ]\n    }\n}\n```\n\n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://media1.giphy.com/media/xT4uQqlFbaPRQkhiQE/giphy.gif\"\u003e\n   \u003cbr\u003e\n   Get it? JASON 🤣\n\u003c/p\u003e\n\nNotice that fields like author, categories, and tags appear as numbers, IDs of their respective\ntypes. The `_links` section contains HTTP links to those resources. Here is the same request with\n`_embed` added at the end of the URL:\n\n```json\n{\n    \"id\": 2267743,\n    \"date\": \"2017-11-11T10:00:23\",\n    \"date_gmt\": \"2017-11-11T15:00:23\",\n    \"guid\": {\n        \"rendered\": \"https://www.wired.com/?p=2267743\"\n    },\n    \"modified\": \"2017-11-10T18:37:44\",\n    \"modified_gmt\": \"2017-11-10T23:37:44\",\n    \"slug\": \"review-incase-noviconnected-travel-roller\",\n    \"type\": \"post\",\n    \"link\": \"https://www.wired.com/2017/11/review-incase-noviconnected-travel-roller/\",\n    \"title\": {\n        \"rendered\": \"Review: Incase NoviConnected Travel Roller\"\n    },\n    \"content\": {\n        \"rendered\": \"\u003cp\u003eIf you‚Äôre the kind of person who will arrive at the airport without charging your phone or laptop, you are also the kind of person who will forget to charge the battery bank in your smart luggage. I discovered this when a three-hour delay kicked off my weekend trip with Incase‚Äôs NoviConnected carry-on suitcase. Sorry, but it‚Äôs true.\u003c/p\u003e\\n\u003cp\u003eAt home, it took six hours of wall charging at home to get the NoviConnected‚Äôs 10,050-mAh battery up to 100%. A few days later, I packed my bag, headed to the airport and waited at the gate. By the time I realized I needed to charge my phone, the suitcase‚Äôs battery had gone down to 76% after doing nothing, with an estimated one hour and 23 minutes‚Äô worth of juice. \u003c/p\u003e\\n\u003cp\u003eCharging my phone back up to 80% took the suitcase‚Äôs battery down to 19%. What about charging a laptop? Forget about it. The bag‚Äôs battery bank is strictly for small devices only and it\u0026#8217;s best conserved for an emergency situation. Since everything from your boarding pass to your hotel reservation is on your phone, you\u0026#8217;ll need that to be juiced up, so it\u0026#8217;s not a total loss. Like the rest of the bag‚Äôs smart features, it may promise more than it can actually deliver.\u003c/p\u003e\\n\u003cp\u003eFor example, take the suitcase‚Äôs Smart Luggage tracker. It uses Bluetooth and has a range of up to 33 feet. On a full flight, I ended up checking my carry-on at the airline‚Äôs request. When I got to baggage claim, I glanced at the Incase app to determine my luggage‚Äôs whereabouts and found out that my bag was out of range. For fifteen minutes, I waited at the baggage carousel like an ordinary plebe, trapped in a seemingly endless purgatory, mired in deep existential uncertainty. \u003c/p\u003e\\n\u003cp\u003eEven walking across my house prompted a notification from my phone, alerting me that the suitcase\u0026#8212;safely in my office\u0026#8212;was no longer within range. If you want a smart luggage tracker, it should probably use GPS instead of Bluetooth.\u003c/p\u003e\\n\\n\u003cdiv id=\\\"wired-tired\\\" class=\\\"carve col sm-col-18 med-col-9 big-col-9 fader proxima gray-5 relative\\\" data-share data-js=\\\"fader\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cdiv id=\\\"wired-tired-text\\\" class=\\\"pad-t-med\\\"\u003e\\n\\t\\t\u003ch2 class=\\\"tungsten no-marg\\\"\u003eIncase NoviConnected Travel Roller\u003c/h2\u003e\\n\\t\\t\u003ch3 class=\\\"tungsten gray-5 no-marg\\\"\u003e5/10\u003c/h3\u003e\\t\\t\u003cdiv class=\\\"border-box border-t hide-mob hide-sm hide-med\\\"\u003e\\n\\t\\t\\t\u003ca class=\\\"byline gray-5 no-underline no-hover\\\" href=\\\"#\\\" data-ui=\\\"howRateOpen\\\"\u003eLearn How We Rate\u003c/a\u003e\\n\\t\\t\u003c/div\u003e\\n\\t\\t\u003ch5 class=\\\"brandon uppercase border-t\\\"\u003eWired\u003c/h5\u003e\\n\\t\\t\u003cp class=\\\"gray-5\\\"\u003eBeautiful. Durable. Hubless wheels roll smoothly and easily and are easily replaceable. Battery bank will save you in a pinch. Comes with rain cover.\u003c/p\u003e\\n\\t\\t\u003ch5 class=\\\"brandon uppercase border-t\\\"\u003eTired\u003c/h5\u003e\\n\\t\\t\u003cp class=\\\"gray-5\\\"\u003eBluetooth tracker makes no sense. No side handle. Battery life was disappointing.\u003c/p\u003e\\n\\t\\t\u003cdiv class=\\\"border-box border-t border-b gray-5\\\"\u003e\\n\\t\\t\\t\\t\\t\\t\\t\\t\u003ca class=\\\"byline float-l no-underline no-hover\\\" rel=\\\"nofollow\\\" href=\\\"https://www.amazon.com/dp/B076B6LW8T/?tag=w050b-20\\\" target=\\\"_blank\\\"\u003eBuy It Now\u003c/a\u003e\\n\\t\\t\\t\u003cspan class=\\\"byline gray-5\\\"\u003e\u0026nbsp;\u0026nbsp;|\u0026nbsp;\u0026nbsp;Incase\u003c/span\u003e\\n\\t\\t\\t\\t\\t\\t\\t\u003c/div\u003e\\n\\t\u003c/div\u003e\\n\\n\\t\u003cdiv id=\\\"how-rate\\\" class=\\\"hide hide-mob hide-sm hide-med bg-white absolute bottom left right\\\" data-js='howWeRate'\u003e\\n\\t\\t\u003ca data-ui=\\\"howRateClose\\\" class=\\\"float-r no-underline no-hover marg-t-med\\\"\u003e\\n\\t\\t\\t\u003ci aria-hidden=\\\"true\\\" role=\\\"presentation\\\" class=\\\"ui ui-close opacity-3 r-align\\\"\u003e\u003c/i\u003e\\n\\t\\t\u003c/a\u003e\\n\\t\\t\u003ch4 class=\\\"tungsten clamp-6 pad-t-med pad-b-med clearfix\\\"\u003eHow We Rate\u003c/h4\u003e\\n\\t\\t\u003cdiv class=\\\"border-box border-t border-b pad-t-med pad-b-med\\\"\u003e\\n\\t\\t\\t\u003cul class=\\\"score-key list-none no-marg\\\"\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e1/10\u003c/span\u003eA complete failure in every way\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e2/10\u003c/span\u003eSad, really\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e3/10\u003c/span\u003eSerious flaws; proceed with caution\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e4/10\u003c/span\u003eDownsides outweigh upsides\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e5/10\u003c/span\u003eRecommended with reservations\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e6/10\u003c/span\u003eSolid with some issues\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e7/10\u003c/span\u003eVery good, but not quite great\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e8/10\u003c/span\u003eExcellent, with room to kvetch\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e9/10\u003c/span\u003eNearly flawless\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\\t\u003cli class=\\\"no-marg\\\"\u003e\\n\\t\\t\\t\\t\\t\u003cspan class=\\\"tungsten marg-r-micro inline-block v-align-b score\\\"\u003e10/10\u003c/span\u003eMetaphysical perfection\\n\\t\\t\\t\\t\u003c/li\u003e\\n\\t\\t\\t\u003c/ul\u003e\\n\\t\\t\u003c/div\u003e\\n\\t\u003c/div\u003e\\n\u003c/div\u003e\\n\\n\u003cp\u003eBut as a piece of ordinary luggage, the bag performed admirably. It has a simple, durable makrolon polycarbonate shell that showed little wear after a few trips around the baggage carousel. The navy color of my sample case was elegant and surprisingly distinctive among a billion black bags. The interior, with mesh zip pockets and an included laundry bag, easily accommodated several days‚Äô worth of clothes and shoes. \u003c/p\u003e\\n\u003cp\u003eThe bag was impossible to tip over, even with a tote bag hooked on top. The hubless wheels rolled smoothly‚Äîalmost a little too smoothly, as the suitcase escaped me when my attention wandered on a slanting parking lot garage floor‚Äîand it‚Äôs simple to pop the wheels on and off if you need to fit the suitcase into an overhead compartment. This model also carries a lifetime warranty from Incase, so the wheels are covered if they break (the power bank\u0026#8217;s embedded battery has a two-year limited warranty). \u003c/p\u003e\\n\u003cp\u003eThe integrated TSA-approved lock works like you\u0026#8217;d expect, and it proved useful when I ended up checking my carry-on to save overhead space on two packed flights. However, I would have appreciated a side handle to make it easier to hoist around. \u003c/p\u003e\\n\u003cp\u003eAs holiday travel season approaches, you do have to find a way to get all your stuff from Point A to Point B without carrying it all in your arms. Incase is known for their attractive, Apple-compatible accessories and bags, and the NoviConnected is no exception. It‚Äôs elegant, rolls with ease, and has the capacity to save your tuchus right when you need it most‚Äîwhen your phone is about to die and you have no way to text your ride or check your car rental confirmation number.\u003c/p\u003e\\n\u003cp\u003eBut there are just too many other moderately-priced smart carry-ons out there right now with features that make a bit more sense, like a true GPS locator that can tell you if your baggage has made it to your destination. Or just bring your own \u003ca rel=\\\"nofollow\\\" href=\\\"https://www.amazon.com/dp/B0194WDVHI/?tag=w050b-20\\\" target=\\\"_blank\\\"\u003espare battery\u003c/a\u003e and pop a \u003ca rel=\\\"nofollow\\\" href=\\\"https://www.amazon.com/dp/B01MF9VQOP/?tag=w050b-20\\\" target=\\\"_blank\\\"\u003eTile tracker\u003c/a\u003e into your current luggage and call it a day.\u003c/p\u003e\\n\"\n    },\n    \"excerpt\": {\n        \"rendered\": \"\u003cp\u003eA great piece of luggage is brought down by its poor smart features.\u003c/p\u003e\\n\"\n    },\n    \"author\": 609,\n    \"featured_media\": 2267773,\n    \"comment_status\": \"open\",\n    \"ping_status\": \"closed\",\n    \"sticky\": false,\n    \"format\": \"standard\",\n    \"categories\": [\n        36,\n        83082,\n        4\n    ],\n    \"tags\": [\n        106059,\n        106060\n    ],\n    \"series\": [],\n    \"admin-settings\": [],\n    \"_links\": {\n        \"self\": [\n            {\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/posts/2267743\"\n            }\n        ],\n        \"collection\": [\n            {\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/posts\"\n            }\n        ],\n        \"about\": [\n            {\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/types/post\"\n            }\n        ],\n        \"author\": [\n            {\n                \"embeddable\": true,\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/users/609\"\n            }\n        ],\n        \"replies\": [\n            {\n                \"embeddable\": true,\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/comments?post=2267743\"\n            }\n        ],\n        \"version-history\": [\n            {\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/posts/2267743/revisions\"\n            }\n        ],\n        \"wp:featuredmedia\": [\n            {\n                \"embeddable\": true,\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/media/2267773\"\n            }\n        ],\n        \"wp:attachment\": [\n            {\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/media?parent=2267743\"\n            }\n        ],\n        \"wp:term\": [\n            {\n                \"taxonomy\": \"category\",\n                \"embeddable\": true,\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/categories?post=2267743\"\n            },\n            {\n                \"taxonomy\": \"post_tag\",\n                \"embeddable\": true,\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/tags?post=2267743\"\n            },\n            {\n                \"taxonomy\": \"series\",\n                \"embeddable\": true,\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/series?post=2267743\"\n            },\n            {\n                \"taxonomy\": \"admin-settings\",\n                \"embeddable\": true,\n                \"href\": \"https://www.wired.com/wp-json/wp/v2/admin-settings?post=2267743\"\n            }\n        ],\n        \"curies\": [\n            {\n                \"name\": \"wp\",\n                \"href\": \"https://api.w.org/{rel}\",\n                \"templated\": true\n            }\n        ]\n    },\n    \"_embedded\": {\n        \"author\": [\n            {\n                \"id\": 609,\n                \"name\": \"Adrienne So\",\n                \"url\": \"\",\n                \"description\": \"\",\n                \"link\": \"https://www.wired.com/author/adrienne-so/\",\n                \"slug\": \"adrienne-so\",\n                \"avatar_urls\": {\n                    \"24\": \"https://secure.gravatar.com/avatar/7c93ee8a4e06687ba5bf692dedc794b6?s=24\u0026d=mm\u0026r=g\",\n                    \"48\": \"https://secure.gravatar.com/avatar/7c93ee8a4e06687ba5bf692dedc794b6?s=48\u0026d=mm\u0026r=g\",\n                    \"96\": \"https://secure.gravatar.com/avatar/7c93ee8a4e06687ba5bf692dedc794b6?s=96\u0026d=mm\u0026r=g\"\n                },\n                \"_links\": {\n                    \"self\": [\n                        {\n                            \"href\": \"https://www.wired.com/wp-json/wp/v2/users/609\"\n                        }\n                    ],\n                    \"collection\": [\n                        {\n                            \"href\": \"https://www.wired.com/wp-json/wp/v2/users\"\n                        }\n                    ]\n                }\n            }\n        ],\n        \"wp:featuredmedia\": [\n            {\n                \"id\": 2267773,\n                \"date\": \"2017-11-10T16:10:13\",\n                \"slug\": \"incase-fa\",\n                \"type\": \"attachment\",\n                \"link\": \"https://www.wired.com/2017/11/review-incase-noviconnected-travel-roller/incase-fa/\",\n                \"title\": {\n                    \"rendered\": \"incase-FA.jpg\"\n                },\n                \"author\": 10393,\n                \"alt_text\": \"\",\n                \"media_type\": \"image\",\n                \"mime_type\": \"image/jpeg\",\n                \"media_details\": {\n                    \"width\": 2236,\n                    \"height\": 1119,\n                    \"file\": \"2017/11/incase-FA.jpg\",\n                    \"sizes\": {\n                        \"thumbnail\": {\n                            \"file\": \"incase-FA-150x150-e1510348383201.jpg\",\n                            \"width\": 150,\n                            \"height\": 150,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-150x150-e1510348383201.jpg\"\n                        },\n                        \"medium\": {\n                            \"file\": \"incase-FA-300x150.jpg\",\n                            \"width\": 300,\n                            \"height\": 150,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-300x150.jpg\"\n                        },\n                        \"medium_large\": {\n                            \"file\": \"incase-FA-768x384.jpg\",\n                            \"width\": 768,\n                            \"height\": 384,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-768x384.jpg\"\n                        },\n                        \"large\": {\n                            \"file\": \"incase-FA-1024x512.jpg\",\n                            \"width\": 1024,\n                            \"height\": 512,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-1024x512.jpg\"\n                        },\n                        \"200-100-thumbnail\": {\n                            \"file\": \"incase-FA-200x100-e1510348370909.jpg\",\n                            \"width\": 200,\n                            \"height\": 100,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-200x100-e1510348370909.jpg\"\n                        },\n                        \"200-200-thumbnail\": {\n                            \"file\": \"incase-FA-200x200-e1510348352675.jpg\",\n                            \"width\": 200,\n                            \"height\": 200,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-200x200-e1510348352675.jpg\"\n                        },\n                        \"500-500-thumbnail\": {\n                            \"file\": \"incase-FA-500x500-e1510348327817.jpg\",\n                            \"width\": 500,\n                            \"height\": 500,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-500x500-e1510348327817.jpg\"\n                        },\n                        \"660-single-full\": {\n                            \"file\": \"incase-FA-660x330.jpg\",\n                            \"width\": 660,\n                            \"height\": 330,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-660x330.jpg\"\n                        },\n                        \"315-single-full\": {\n                            \"file\": \"incase-FA-315x158.jpg\",\n                            \"width\": 315,\n                            \"height\": 158,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-315x158.jpg\"\n                        },\n                        \"thumbnail-post\": {\n                            \"file\": \"incase-FA-300x150-e1510348340197.jpg\",\n                            \"width\": 300,\n                            \"height\": 150,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-300x150-e1510348340197.jpg\"\n                        },\n                        \"600-338-full\": {\n                            \"file\": \"incase-FA-600x338-e1510348316142.jpg\",\n                            \"width\": 600,\n                            \"height\": 338,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-600x338-e1510348316142.jpg\"\n                        },\n                        \"600-450-full\": {\n                            \"file\": \"incase-FA-600x450-e1510348304347.jpg\",\n                            \"width\": 600,\n                            \"height\": 450,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-600x450-e1510348304347.jpg\"\n                        },\n                        \"929-697-full\": {\n                            \"file\": \"incase-FA-929x697-e1510348296219.jpg\",\n                            \"width\": 929,\n                            \"height\": 697,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-929x697-e1510348296219.jpg\"\n                        },\n                        \"125-94-thumbnail\": {\n                            \"file\": \"incase-FA-125x94-e1510348402271.jpg\",\n                            \"width\": 125,\n                            \"height\": 94,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-125x94-e1510348402271.jpg\"\n                        },\n                        \"929-523-full\": {\n                            \"file\": \"incase-FA-929x523-e1510348287351.jpg\",\n                            \"width\": 929,\n                            \"height\": 523,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-929x523-e1510348287351.jpg\"\n                        },\n                        \"default-top-art\": {\n                            \"file\": \"incase-FA-582x291.jpg\",\n                            \"width\": 582,\n                            \"height\": 291,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-582x291.jpg\"\n                        },\n                        \"wide-image\": {\n                            \"file\": \"incase-FA-932x466.jpg\",\n                            \"width\": 932,\n                            \"height\": 466,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-932x466.jpg\"\n                        },\n                        \"inset-image\": {\n                            \"file\": \"incase-FA-289x145.jpg\",\n                            \"width\": 289,\n                            \"height\": 145,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-289x145.jpg\"\n                        },\n                        \"text-column-width\": {\n                            \"file\": \"incase-FA-482x241.jpg\",\n                            \"width\": 482,\n                            \"height\": 241,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-482x241.jpg\"\n                        },\n                        \"facebook-og\": {\n                            \"file\": \"incase-FA-1200x630-e1510348277739.jpg\",\n                            \"width\": 1200,\n                            \"height\": 630,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA-1200x630-e1510348277739.jpg\"\n                        },\n                        \"full\": {\n                            \"file\": \"incase-FA.jpg\",\n                            \"width\": 2236,\n                            \"height\": 1119,\n                            \"mime_type\": \"image/jpeg\",\n                            \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA.jpg\"\n                        }\n                    },\n                    \"image_meta\": {\n                        \"aperture\": \"0\",\n                        \"credit\": \"\",\n                        \"camera\": \"\",\n                        \"caption\": \"\",\n                        \"created_timestamp\": \"0\",\n                        \"copyright\": \"\",\n                        \"focal_length\": \"0\",\n                        \"iso\": \"0\",\n                        \"shutter_speed\": \"0\",\n                        \"title\": \"\",\n                        \"orientation\": \"0\",\n                        \"keywords\": []\n                    }\n                },\n                \"source_url\": \"https://www.wired.com/wp-content/uploads/2017/11/incase-FA.jpg\",\n                \"_links\": {\n                    \"self\": [\n                        {\n                            \"href\": \"https://www.wired.com/wp-json/wp/v2/media/2267773\"\n                        }\n                    ],\n                    \"collection\": [\n                        {\n                            \"href\": \"https://www.wired.com/wp-json/wp/v2/media\"\n                        }\n                    ],\n                    \"about\": [\n                        {\n                            \"href\": \"https://www.wired.com/wp-json/wp/v2/types/attachment\"\n                        }\n                    ],\n                    \"author\": [\n                        {\n                            \"embeddable\": true,\n                            \"href\": \"https://www.wired.com/wp-json/wp/v2/users/10393\"\n                        }\n                    ],\n                    \"replies\": [\n                        {\n                            \"embeddable\": true,\n                            \"href\": \"https://www.wired.com/wp-json/wp/v2/comments?post=2267773\"\n                        }\n                    ]\n                }\n            }\n        ],\n        \"wp:term\": [\n            [\n                {\n                    \"id\": 36,\n                    \"link\": \"https://www.wired.com/category/gear/reviews/accessories/\",\n                    \"name\": \"Accessories\",\n                    \"slug\": \"accessories\",\n                    \"taxonomy\": \"category\",\n                    \"_links\": {\n                        \"self\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/categories/36\"\n                            }\n                        ],\n                        \"collection\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/categories\"\n                            }\n                        ],\n                        \"about\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/taxonomies/category\"\n                            }\n                        ],\n                        \"up\": [\n                            {\n                                \"embeddable\": true,\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/categories/4\"\n                            }\n                        ],\n                        \"wp:post_type\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/posts?categories=36\"\n                            },\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/video?categories=36\"\n                            }\n                        ],\n                        \"curies\": [\n                            {\n                                \"name\": \"wp\",\n                                \"href\": \"https://api.w.org/{rel}\",\n                                \"templated\": true\n                            }\n                        ]\n                    }\n                },\n                {\n                    \"id\": 83082,\n                    \"link\": \"https://www.wired.com/category/gear/\",\n                    \"name\": \"Gear\",\n                    \"slug\": \"gear\",\n                    \"taxonomy\": \"category\",\n                    \"_links\": {\n                        \"self\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/categories/83082\"\n                            }\n                        ],\n                        \"collection\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/categories\"\n                            }\n                        ],\n                        \"about\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/taxonomies/category\"\n                            }\n                        ],\n                        \"wp:post_type\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/posts?categories=83082\"\n                            },\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/video?categories=83082\"\n                            }\n                        ],\n                        \"curies\": [\n                            {\n                                \"name\": \"wp\",\n                                \"href\": \"https://api.w.org/{rel}\",\n                                \"templated\": true\n                            }\n                        ]\n                    }\n                },\n                {\n                    \"id\": 4,\n                    \"link\": \"https://www.wired.com/category/gear/reviews/\",\n                    \"name\": \"Reviews\",\n                    \"slug\": \"reviews\",\n                    \"taxonomy\": \"category\",\n                    \"_links\": {\n                        \"self\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/categories/4\"\n                            }\n                        ],\n                        \"collection\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/categories\"\n                            }\n                        ],\n                        \"about\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/taxonomies/category\"\n                            }\n                        ],\n                        \"up\": [\n                            {\n                                \"embeddable\": true,\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/categories/83082\"\n                            }\n                        ],\n                        \"wp:post_type\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/posts?categories=4\"\n                            },\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/video?categories=4\"\n                            }\n                        ],\n                        \"curies\": [\n                            {\n                                \"name\": \"wp\",\n                                \"href\": \"https://api.w.org/{rel}\",\n                                \"templated\": true\n                            }\n                        ]\n                    }\n                }\n            ],\n            [\n                {\n                    \"id\": 106059,\n                    \"link\": \"https://www.wired.com/tag/incase/\",\n                    \"name\": \"incase\",\n                    \"slug\": \"incase\",\n                    \"taxonomy\": \"post_tag\",\n                    \"_links\": {\n                        \"self\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/tags/106059\"\n                            }\n                        ],\n                        \"collection\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/tags\"\n                            }\n                        ],\n                        \"about\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/taxonomies/post_tag\"\n                            }\n                        ],\n                        \"wp:post_type\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/posts?tags=106059\"\n                            },\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/podcast?tags=106059\"\n                            },\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/video?tags=106059\"\n                            }\n                        ],\n                        \"curies\": [\n                            {\n                                \"name\": \"wp\",\n                                \"href\": \"https://api.w.org/{rel}\",\n                                \"templated\": true\n                            }\n                        ]\n                    }\n                },\n                {\n                    \"id\": 106060,\n                    \"link\": \"https://www.wired.com/tag/luggage/\",\n                    \"name\": \"luggage\",\n                    \"slug\": \"luggage\",\n                    \"taxonomy\": \"post_tag\",\n                    \"_links\": {\n                        \"self\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/tags/106060\"\n                            }\n                        ],\n                        \"collection\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/tags\"\n                            }\n                        ],\n                        \"about\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/taxonomies/post_tag\"\n                            }\n                        ],\n                        \"wp:post_type\": [\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/posts?tags=106060\"\n                            },\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/podcast?tags=106060\"\n                            },\n                            {\n                                \"href\": \"https://www.wired.com/wp-json/wp/v2/video?tags=106060\"\n                            }\n                        ],\n                        \"curies\": [\n                            {\n                                \"name\": \"wp\",\n                                \"href\": \"https://api.w.org/{rel}\",\n                                \"templated\": true\n                            }\n                        ]\n                    }\n                }\n            ],\n            [],\n            []\n        ]\n    }\n}\n```\n\nNotice that there is now an `_embed` section with the actual data for authors, categories, and tags.\nLater in the workshop, we will leverage this data to display our pages.\n\nIn order to simplify things further, we will be using an NPM package called\n[wpapi](https://github.com/wp-api/node-wpapi). This package will allow us to call WordPress API\nendpoints through a set of simple functions. Let's install the package:\n\n```sh\nyarn add wpapi\n```\n\nThen, create a file called `api.js` at the root of your project with the following code:\n\n```js\nimport WPAPI from 'wpapi';\n\nlet endpoint = 'https://www.wired.com/wp-json';\nif (typeof window !== 'undefined') {\n  endpoint = `https://cors-anywhere.herokuapp.com/${endpoint}`;\n}\n\nconst api = new WPAPI({ endpoint });\nexport default api;\n```\n\nThe `wpapi` package exposes a `WPAPI` constructor taking a base URL as parameter. In this case, we\nwill be using the wired.com API as explained in the introduction. However, remember that this code\nwill be running in the server *and* in the browser. If our browser tries to make an HTTP request to\nwired.com's WordPress API from a different origin – localhost:3000 – our browser will refuse to let\nus see the response due to cross-origin security rules. “Cors Anywhere” is a freely available proxy\nserver that can take care of this by making the request on our behalf and adding the appropriate\n[CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) headers to make our browser comply.\nExplaining CORS is beyond the scope of this workshop, but feel free to follow the links to learn\nmore. The **TL;DR** is that the server will be using the straight URL, and the browser will go\nthrough a proxy server.\n\n:warning: **NOTE**: Ideally, we will have as little of this kind of “if server / if client” code in\nour application. Next.js should be able to deal with it for us in most cases.\n\nOnce this file is created, let's use it in our `pages/blog.js` file. Overwrite the file with the\nfollowing content:\n\n```js\nimport React from 'react';\nimport Link from 'next/link';\nimport api from '../api';\n\nclass Blog extends React.Component {\n  static async getInitialProps() {\n    const posts = await api.posts().embed();\n    return { posts };\n  }\n\n  render() {\n    const { posts } = this.props;\n\n    return (\n      \u003cdiv\u003e\n        \u003ch1\u003eRecent blog posts\u003c/h1\u003e\n        {\n          posts.map(post =\u003e (\n            \u003cdiv key={post.id}\u003e\n              \u003cLink\n                href={{\n                  pathname: '/blogpost',\n                  query: {\n                    slug: post.slug,\n                  },\n                }}\n              \u003e\n                \u003ca\u003e\n                  \u003ch2 dangerouslySetInnerHTML={{ __html: post.title.rendered }} /\u003e\n                \u003c/a\u003e\n              \u003c/Link\u003e\n              \u003cp\u003e{post.date}\u003c/p\u003e\n              {\n                !!post._embedded['wp:featuredmedia'] \u0026\u0026\n                \u003cimg\n                  width={500}\n                  src={post._embedded['wp:featuredmedia'][0].source_url}\n                  alt={post._embedded['wp:featuredmedia'][0].alt_text}\n                /\u003e\n              }\n              \u003cdiv dangerouslySetInnerHTML={{ __html: post.excerpt.rendered }} /\u003e\n            \u003c/div\u003e\n          ))\n        }\n      \u003c/div\u003e\n    );\n  }\n}\n\nexport default Blog;\n```\n\nFirst, we have transformed the `Blog` page component from a function to a class. This allows us to\nadd the static `getInitialProps` function. This function is not part of React but of Next.js. The\nfunction will be executed prior to rendering the page component. This function can return a Promise,\nwhich we are doing here through the use of the `async` keyword. Next.js will wait for this Promise\nto resolve before rendering. The return value of the Promise will be passed as props to the React\ncomponent. In `render()`, we are using some simple, pure React code to render the recent posts list.\nWe will come back to this code later and refactor it.\n\nNotice the new usage of the `\u003cLink\u003e` component. We can build the URL to each post by passing a\n`query` prop in addition to the `href` prop. This will build links like `/blogpost?slug=the-slug`.\nThe links are currently not pretty, but will come back and fix it later.\n\nNavigate to `/blog` to see your new blog listing. Now that we are loading some data, it would be a\ngood time to check that Next.js is working correctly. If you navigate directly to `/blog` from your\nbrowser's address bar and view source, you will see that the server sent all the post data. Looking\nat your console, you should not see any HTTP requests made in the browser. However, if you navigate\nto `/` and click on the `/blog` link, you will see the same HTTP request being made in your browser,\nwith the addition of the Cors Anywhere proxy.\n\nThis concludes the blog listing section. Next, let's make those `/blogpost?slug=...` links work!\n\n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://media1.giphy.com/media/3o6MbhdrQxaTJxhOiQ/giphy.gif\"\u003e\n\u003c/p\u003e\n\n---\n\n## Adding the single blog post page :page_facing_up:\nThis will be very similar to the previous section, so we will go a bit faster. Create a file called\n`blogpost.js` in the `pages` directory, and put the following code in it:\n\n```js\nimport React from 'react';\nimport Link from 'next/link';\nimport api from '../api';\n\nclass BlogPost extends React.Component {\n  static async getInitialProps({ query: { slug } }) {\n    const post = await api.posts().slug(slug).embed();\n    return { post: post[0] };\n  }\n\n  render() {\n    const { post } = this.props;\n\n    return (\n      \u003cdiv\u003e\n        \u003ch1 dangerouslySetInnerHTML={{ __html: post.title.rendered }} /\u003e\n        \u003cp\u003e{post.date}\u003c/p\u003e\n        \u003cp\u003eBy {post._embedded.author[0].name}\u003c/p\u003e\n        {\n          !!post._embedded['wp:featuredmedia'] \u0026\u0026\n          \u003cimg\n            width={500}\n            src={post._embedded['wp:featuredmedia'][0].source_url}\n            alt={post._embedded['wp:featuredmedia'][0].alt_text}\n          /\u003e\n        }\n        \u003cdiv dangerouslySetInnerHTML={{ __html: post.content.rendered }} /\u003e\n      \u003c/div\u003e\n    );\n  }\n}\n\nexport default BlogPost;\n```\n\nThe main differences are the use of the `query` parameter inside `getInitialProps`. This is one of\nthe many parameters that Next.js passes to the function. It allows us to decouple ourselves from the\nURL and the environment. `query` will be passed the same both on the client and the server. We\nextract the slug, and pass it to the `.slug()` method of `wpapi`. In `render()`, we simply use\n`post.content` instead of `post.excerpt` to display the full post.\n\nNow that we have a few pages going, you can see that we don't have a common layout between pages.\nLet's fix this in the next section by wrapping each page with a common header and footer.\n\n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://media1.giphy.com/media/zcCGBRQshGdt6/giphy.gif\"\u003e\n\u003c/p\u003e\n\n---\n\n## Adding a common layout :house:\nThere are many ways to add a common layout to a Next.js application. In our case, we will use a\nsimple React higher-order component. That's just a fancy word for saying “component that wraps\nanother component”.\n\nCreate a directory called `components` at the root of your application. This is by no means a\nspecial directory to Next.js. It will only allow us to separate our code. In this directory, create\na new file called `withLayout.js` with the following code:\n\n```js\nimport React from 'react';\nimport Link from 'next/link';\n\nconst withLayout = (WrappedComponent) =\u003e {\n  class WithLayout extends React.Component {\n    static async getInitialProps(...args) {\n      if (typeof WrappedComponent.getInitialProps === 'function') {\n        return WrappedComponent.getInitialProps(...args);\n      }\n\n      return {};\n    }\n\n    render() {\n      const { props } = this;\n\n      return (\n        \u003cdiv\u003e\n          \u003cheader\u003e\n            \u003cnav\u003e\n              \u003cul\u003e\n                \u003cli\u003e\u003cLink href=\"/\"\u003e\u003ca\u003ehome\u003c/a\u003e\u003c/Link\u003e\u003c/li\u003e\n                \u003cli\u003e\u003cLink href=\"/blog\"\u003e\u003ca\u003eblog\u003c/a\u003e\u003c/Link\u003e\u003c/li\u003e\n              \u003c/ul\u003e\n            \u003c/nav\u003e\n          \u003c/header\u003e\n          \u003cmain\u003e\n            \u003cWrappedComponent {...props} /\u003e\n          \u003c/main\u003e\n          \u003cfooter\u003e\n            this is the footer\n          \u003c/footer\u003e\n        \u003c/div\u003e\n      );\n    }\n  }\n\n  WithLayout.displayName = `withLayout(${WrappedComponent.displayName || WrappedComponent.name || 'UnknownComponent'})`;\n  return WithLayout;\n};\n\nexport default withLayout;\n```\n\nIn this module, we export a function called `withLayout`. This function takes a React component and\nreturns a new React component class. The class has a `render()` method, but also a\n`getInitialProps` static function. This is necessary due to how we will be wrapping page components.\nThe `getInitialProps` function simply proxies to the wrapped component's `getInitialProps` if it\nexists. The `.displayName` bit at the end is especially helpful with React Developer Tools. It will\nallow us to clearly identify the `withLayout` wrapper as can be seen in the following screenshot:\n\n![react dev tools view of withLayout](withLayout.png)\n\nNow that the component has been created, let's use it to wrap our page components.\n\nOpen `pages/index.js` and make the following changes:\n\n```js\n// Add this at the top\nimport withLayout from '../components/withLayout';\n\n// Modify the last line to read\nexport default withLayout(HomePage);\n```\n\n:warning: **NOTE**: You might have to restart your Next.js server after adding the layout.\n\nThat's it. The component's code stays the same, but we export a wrapped version of it instead. Now,\ndo the same with the other two pages and look at the result in your browser.\n\n---\n\n## How about some SEO? :mag:\nLet's look back at what we've done so far. Using a minimal amount of code, we were able to get a\nserver-rendered, universal React theme for a WordPress blog. This has given us all the benefits of\nmodern front-end technologies as well as the benefits of server-rendered SEO: crawler can see our\ncontent, and users can see it sooner because it comes with the initial HTTP response.\n\nOne major thing that is missing for SEO is a useful title for each page. As you might have expected,\nNext.js makes it easy to add elements to the `\u003chead\u003e` of the page. Let's start by adding a title to\nthe home page. Open `pages/index.js` and make the following changes:\n\n```js\n// Add this line at the top\nimport Head from 'next/head';\n\n// Add this bit after the opening \u003cdiv\u003e\n\u003cHead\u003e\n  \u003ctitle\u003eWelcome to my site\u003c/title\u003e\n\u003c/Head\u003e\n```\n\nThen, do the same with the blog listing and single blog post pages. For the single blog post page,\nsimply use `{post.title.rendered}`.\n\nWhat about other SEO elements like `\u003cmeta description\u003e` an so on? The popular\n[Yoast SEO](https://yoast.com/wordpress/plugins/seo/) WordPress plugin allows you to easily add the\ndata for these tags from your administrative interface, and they are exposed using\n[WordPress' Post Meta Data](https://codex.wordpress.org/Post_Meta_Data_Section) feature. Sadly, the\nWordPress API does not expose Post Meta Data by default. The\n[`register_rest_field`](https://developer.wordpress.org/rest-api/extending-the-rest-api/modifying-response/)\nWordPress function allows an administrator to add certain fields to a standard post or page\nresponse.\n\nAn example of this is the\n[wp-api-yoast-meta](https://plugins.trac.wordpress.org/browser/wp-api-yoast-meta/trunk/plugin.php)\nWordPress plugin. The linked source code shows a clear usage of this function. If you install this\nplugin on the WordPress side, you will be able to access Yoast meta directly from the post response.\n\nThen, you can add whatever fields you need under the `\u003cHead\u003e` tag in your React page component.\n\n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://media3.giphy.com/media/5fkzHxGP920Ra/giphy.gif\"\u003e\n\u003c/p\u003e\n\n---\n\n## My blog looks like :poop:\nSo far, we have worked hard to create a meaningful WordPress base theme from the content\nperspective. However, our blog currently looks like :poop: Let's fix this with some CSS.\n\nNext.js uses [styled-jsx](https://github.com/zeit/styled-jsx) to allow scoped styling of components.\nThis is one of the many available styling solutions for React components, and it's the recommended\nway to style with Next.js. While you can use other methods, they might require a lot of workarounds\nto work perfectly on both the server and client.\n\nThe **TL;DR** of styled-jsx is that you add a `\u003cstyle\u003e` tag as part of what you return from your\n`render()` method, and styled-jsx will take care of the rest. This `\u003cstyle\u003e` tag is nothing like\nregular CSS though. The class names you include in there are **scoped** to the React component they\nbelong to.\n\nAs an example, let's try to add a background color to the `\u003cheader\u003e` element in the `withLayout`\ncomponent. Open `components/withLayout.js` and make the following changes:\n\n```js\n// Add this bit after the opening \u003cdiv\u003e in the WrappedComponent\n\u003cstyle jsx\u003e{`\n  header {\n    background-color: lightgrey;\n  }\n`}\u003c/style\u003e\n```\n\nIf this were regular CSS, we would probably not target `header` only. It is a bit too generic. With\nstyled-jsx however, things are different. Navigate to your site and use your browser's element\ninspector. You will notice that each element that is part of `WithLayout` has had a seemingly\nrandom class name added to it, and all the styles you wrote inside `\u003cstyle jsx\u003e` have had this same\nclass name appended to them, effectively scoping the CSS to this component:\n\n![styled jsx scopes styles](styled-jsx.png)\n\nIf you are using WebStorm to do this workshop, you might have noticed something weird: there is no\nsyntax highlighting for your CSS code. So much for the benefits of scoped styled-jsx...\n\n![styled jsx without syntax highlighting](styled-jsx-no-highlighting.png)\n\nIt turns out that WebStorm has a neat feature that makes it somewhat easy to re-enable this syntax\nhighlighting. Simply add the line `{/* language=CSS */}` before the `\u003cstyle jsx\u003e` tag:\n\n![styled jsx with syntax highlighting](styled-jsx-with-syntax-highlighting.png)\n\nSince the `withLayout` higher-order component is used across all pages, it is also a good place to\nadd global styles. Make the following changes to `withLayout.js`:\n\n```js\n// Add this bit after the previous \u003cstyle jsx\u003e tag closes\n{/* language=CSS */}\n\u003cstyle jsx global\u003e\n  {`\n    body {\n      font-family: Helvetica, arial, sans-serif;\n    }\n  `}\n\u003c/style\u003e\n```\n\nThis will apply the `body` styles without scoping them.\n\n:warning: **NOTE**: Even though these styles are defined as “global”, they will only apply if the\ncomponent that includes them is mounted. Here, `withLayout` should always be mounted since it wraps\nall pages, so it's the perfect place to do this.\n\nRight now, you might be thinking that that's a bit too much mixing in the same file. If that is the\ncase, know that styled-jsx allows you to\n[keep your CSS in separate files](https://github.com/zeit/styled-jsx#keeping-css-in-separate-files ).\nFollow the link to learn more about how this is done.\n\nNow that you know how styled-jsx works, it's time to personalize your blog! You could take some\ninspiration from the [twentyseventeen demo](https://2017.wordpress.net/) or any other theme that\nyou like, or invent your own.\n\n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://media1.giphy.com/media/uELtzAhhqpRKg/giphy.gif\"\u003e\n\u003c/p\u003e\n\n---\n\n## Enabling “pretty links” for the single post page :heart:\nThe basic page structure of Next.js does not natively support so-called “pretty-links”. If running\nNext.js with the basic command-line like we have been doing, we do not have much control over this.\nEach file in the `pages` directory corresponds to a page URL. However, Next.js provides a NodeJS\nrequest handler that lets us extend it at will. Let's do it!\n\nTo be clear, what we will do in this section is change the single blog post links from:\n\n```\n/blogpost?slug=super-cool-post\n```\n\nto:\n\n```\n/blog/super-cool-post\n```\n\nFor this section, we will be installing the ExpressJS web server. Then, we will create a server\nmodule that will import Next.js' request handler. We will then spin up our own ExpressJS web server,\nsomething that Next normally does for us. In there, we will define a custom route with a path\nparameter, and hand over the control to Next. Then, we will define a catch-all route that will\nsimply foward to Next's handler immediately.\n\nFirst, run `yarn add express`. Then, create a file called `server.js` at the root of your project.\nIn there, add the following code:\n\n```js\nconst express = require('express');\nconst next = require('next');\n\nconst port = parseInt(process.env.PORT, 10) || 3000;\nconst dev = process.env.NODE_ENV !== 'production';\nconst app = next({ dev });\nconst handle = app.getRequestHandler();\nconst server = express();\n\n(async () =\u003e {\n  await app.prepare();\n\n  server.get('/blog/:slug', (req, res) =\u003e (\n    app.render(req, res, '/blogpost', req.params)\n  ));\n\n  server.get('*', (req, res) =\u003e (\n    handle(req, res)\n  ));\n\n  server.listen(port, (err) =\u003e {\n    if (err) throw err;\n    console.log(`Listening on http://0.0.0.0:${port}`);\n  });\n})();\n``` \n\nIf you've used Express before, some of this code will look familiar. The first `server.get` call\nsets up a route with pattern `/blog/:slug:`. In the handler for that route, we simply pass the\nrequest parameters – an object containing the `slug` key – to the Next.js `app.render` function.\nNext will in turn pass these parameters to the `getInitialProps` static function of our single blog\npost page component, and the rest will follow.\n\nThe second `server.get` is our catch-all route. There, we have nothing special to do. Next.js will\nhandle the request from beginning to end through the `handle` function we extracted from it.\n\nTo use this new `server.js` file, open `package.json` and change the `start` script to:\n\n```sh\nNODE_ENV=development node server.js\n```\n\nThen, kill the running server and re-run `yarn start`.\n\nNow that this has been setup, you can manually navigate to `/blog/an-existing-slug` and see the\nresulting page. The final step will be to modify the `\u003cLink\u003e`s generated by the blog listing page.\n\nOpen `pages/blog.js` and change the `\u003cLink\u003e` to the following:\n\n```js\n\u003cLink\n  href={{\n    pathname: '/blogpost',\n    query: {\n      slug: post.slug,\n    },\n  }}\n  as={`/blog/${post.slug}`}\n\u003e\n```\n\nThe `as` prop will make Next.js output the pretty link, but internally Next will use the `href`\nprop to execute the dynamic inter-page navigation. That's all there is to it :)\n\n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://media1.giphy.com/media/I5jjv1kptuazu/giphy.gif\"\u003e\n\u003c/p\u003e\n\n### 404: Dealing with posts that don't exist\nWith this route setup, any URL of the form `/blog/something` will go to our `blogpost.js` page\ncomponent. On the server side, this will always return an HTTP 200 status code, even for posts that\ndon't exist. Let's fix this.\n\nOpen `pages/blogpost.js` and make the following changes:\n\n```js\nclass BlogPost extends React.Component {\n  // Change getInitialProps to the following\n  static async getInitialProps({ query: { slug }, res }) {\n    const post = (await api.posts().slug(slug).embed())[0];\n    if (post) {\n      return { post };\n    }\n  \n    if (res) {\n      res.statusCode = 404;\n    }\n    return { error: true };\n  }\n  \n  render() {\n    // Add this bit to the top of Render\n    if (this.props.error) {\n      return \u003cdiv\u003ePost not found\u003c/div\u003e;\n    }\n    \n    // ...rest of render\n  }\n}\n```\n\nBasically, if the WordPress API returns no post for the slug we need, then we will return an error\npage. In addition to this, if we are running the code on the server – the `if (res)` bit – then we\nwill set the response's status code to 404 to help with our SEO.\n\n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://media1.giphy.com/media/10oRQhnkcc72Le/giphy.gif\"\u003e\n\u003c/p\u003e\n\n---\n\n## Interactive feature: infinite scroll! :boom:\nLet's take advantage of the fact that we're using React to easily add an interactive feature:\ninfinite scrolling and loading of posts on the `/blog` page.\n\nWe will implement this using the [react-waypoint](https://github.com/brigade/react-waypoint)\ncomponent. This component fires a callback function when it becomes visible in the viewport. Let's\nstart by installing the package:\n\n```sh\nyarn add react-waypoint\n```\n\nThen, open `pages/blog.js` and make the following changes:\n\n1. Import the component at the top, and add a `PER_PAGE` constant:\n\n   ```js\n   import Waypoint from 'react-waypoint';\n   const PER_PAGE = 10;\n   ```\n\n2. Add an initial state to the `Blog` class:\n\n   ```js\n   class BlogPost extends React.Component {\n     state = {\n       page: 1,\n       loading: false,\n       hasMore: true,\n     }\n   }\n   ```\n\n3. Modify the `wpapi` call in `getInitialProps` to load page 1 with 10 items per page:\n\n   ```js\n   const posts = await api.posts().perPage(PER_PAGE).page(1).embed();\n   ```\n\n4. Since this component will become stateful, we will render posts from the state. Add a\n   `componentWillMount` method with the following code. Note that we use “will mount” because it runs\n   on both the client and the server:\n   \n   ```js\n   this.setState({\n     posts: this.props.posts\n   });\n   ```\n\n5. Modify the `render()` method:\n\n   1. Change the line that reads:\n  \n      ```js\n      const { posts } = this.props;\n      ```\n  \n      to:\n  \n      ```js\n      const { posts } = this.state;\n      ```\n  \n      Otherwise, all the work you did so far will be for nothing, since posts will still be rendered from the initial props!\n\n   2. Add the following before the closing `\u003cdiv\u003e`:\n\n       ```js\n       {this.state.hasMore \u0026\u0026 \u003cWaypoint key={this.state.page} onEnter={this.loadMore} /\u003e}\n       {this.state.loading \u0026\u0026 \u003cp\u003eloading...\u003c/p\u003e}\n       ```\n\n6. Finally, add the `loadMore` method as an instance method of the class:\n\n   ```js\n   loadMore = async () =\u003e {\n     if (this.state.loading || !this.state.hasMore) {\n       return;\n     }\n \n     this.setState({ loading: true});\n     const posts = await api.posts().perPage(PER_PAGE).page(this.state.page + 1).embed();\n     if (posts.length \u003e 0) {\n       this.setState({\n         posts: this.state.posts.concat(posts),\n         page: this.state.page + 1\n       });\n     }\n     else {\n       this.setState({ hasMore: false });\n     }\n     this.setState({\n       loading: false\n     });\n   }\n   ```\n\nThis function will be called by react-waypoint when the bottom of the page is reached. The logic is\nquite straightforward: we load the next page of posts, and concat the result to the end of the posts\narray in our state. If there are no more posts, setting `hasMore` to `false` will completely remove\nthe `\u003cWayPoint\u003e` element from the `render()` method, thereby preventing calling the callback for\nnothing.\n\nThat's it! Load the `/blog` page in your browser and start scrolling.\n\n:warning: **NOTE**: While this is a simple way to implement infinite scrolling, it has one major\npitfall: if you scroll too much, the DOM will become overloaded with too much content that is not\nbeing displayed. This can be wasteful. Some infinite loaders try to mitigate this by replacing the\nelements that are out of view with one single empty div with a fixed CSS height. This is more\ncomplex because it requires knowing the height of each element in advance, but it is certainly\ndoable. It is beyond the scope of this workshop.\n\n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://media.giphy.com/media/oKnaQwRZ2EdOM/giphy.gif\"\u003e\n\u003c/p\u003e\n\n---\n\n## Where to go from here? :eyes:\nIf you finished this workshop early or want to take it home, here are some things you could work on:\n\n* Add error handling.\n* Add categories and tags to the single post page\n* Add a category listing page\n* Add a single category page\n* Add author pages and link to them\n* Componentize: separate the page components from the rendering logic\n* Add page caching at the ExpressJS level\n* Add a search feature\n\nIf you can imagine it, you can do it!\n\n\u003cp align=\"center\"\u003e\n   \u003cimg src=\"https://media2.giphy.com/media/klG0l9x2miZjy/giphy.gif\"\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fziad-saab%2Fwordpress-api-nextjs-theme","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fziad-saab%2Fwordpress-api-nextjs-theme","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fziad-saab%2Fwordpress-api-nextjs-theme/lists"}