How to use React.js to create a Chrome extension in 5 minutes

By Hu Chen

Hacker Tab: Chrome extension to view GitHub trending projects on new tab 📈

Recently I have tried to create a Chrome extension that replaces new tab screen with GitHub trending projects, and it got trending in Product Hunt. I built it using create-react-app and I would like to share how I did it, in 5 minutes.

The Chrome extension I created replaces new tab screen to a custom page which I built with create-react-app. It does not matter which framework you are using — React.js, Vue.js, or vanilla HTML with CSS and JS, as long as you could serve the HTML file in the browser, it will work as a Chrome extension.

In my project, I used create-react-app to create, run, and bundle application. I also used Redux for state management and styled-components for styling.

$ npx create-react-app my-awesome-extension
$ cd my-awesome-extension
$ npm start

You should be able to see the default creact-react-app launching page at http://localhost:3000

Launching page of create-react-app

This tutorial won’t cover how to develop a webpage using React.js, there are tons of great resources out there. I will focus on how to turn the app into a Chrome extension. Now heads down and code your application.

Once you have a working version locally, there are a few other things you will need to config when preparing your extension.

There was a manifest.json file in the public folder which is for PWA purposes or configuring our extension. You will need to replace it with content below, which will be copied over to the build folder during build process, and Chrome will use it as the configuration file of the extension.

The above snippet is what I have used in my extension. You can find other config options as well.

  • version: the version of your extension, you should update this when you plan to release a new version.
  • incognito: choose “split” as we want pages in an incognito window to run in their own incognito process.
  • icons: you need different sizes of Icons to be used in different cases (e.g. Chrome Web Store), normally you will only need 16x16 , 48x48 and 128x128 .
  • chrome_url_overrides: as we are overriding chrome new tab to a custom HTML page, we need to replace newtab to index.html
  • permissions: include the permissions you need in your extension. I am using chrome.storage API to store user preferred programming languages so I have added storage permission. In general practice, you should only add permissions required in the application.

By default, Create React App embeds a small runtime script into index.html during the production build, this is to reduce the number of HTTP requests. But unfortunately, you will see console errors related to CSP. You could turn off the embedding behavior by setting the INLINE_RUNTIME_CHUNK flag to false .

change in package.json :

"build": "INLINE_RUNTIME_CHUNK=false react-scripts build"

Sometimes people would like to switch back to the default Chrome tab to access top sites. We could use chrome.tabs API.

<Button
onClick={() => {
chrome.tabs.getCurrent(tab => {
chrome.tabs.update(tab.id, {
url: 'chrome-search://local-ntp/local-ntp.html',
});
});
}}>
Chrome Tab
</Button>
Please note that to use chrome.tabs API you will need to add tabs to permissions in manifest.json .

Create React App comes with a default favicon.ico in the public folder. For Chrome New Tab, we do not want to have any favicons for the new tab, so we remove it from public folder and also remove the link in index.html .

Because React.js initially loads an empty page before the application is rendered in Javascript, from my experience there is a noticeable flash of blank page before everything gets rendered. This is not a great experience for the user.

There are a few ways to resolve this, one way is to pre-render into static HTML files using react-snapshot or react-snap.

This method does not suit the use case of my extension because it also pre-renders the data from the server, causing initial HTML to always contain outdated repositories before new data comes in.

At the time of writing, I have not figured out how to resolve this issue if using snapshot libraries, so I come with a simpler solution.

I have styled index.html so that it displays a grey logo at the center of the page before the application gets rendered, and it works quite nicely.

Launching Page

Once Javascript finished rendering, root will be replaced with actual content.

Before publishing to Chrome Web Store, it is always good to test locally.

All you need to do is to run npm run build to put everything required into build folder, then launch chrome://extensions/ in Google Chrome.

Test extension locally
  1. Make sure developer mode is on as shown on the image above (1).
  2. Click “Load unpacked” and target “build” folder in your project, it should contain index.html , manifest.json, different sizes of logos, and bundled javascript files (2).
  3. Make sure other extensions like Momentum which also modify new tab is disabled (3).
  4. If you have already published a version in production and you are testing a newer version, you should also disable the production version. (The local version will have a red icon at bottom right corner as in the picture (4))

Opening a new tab should allow you to test your shiny new extensions!

To update the extension, you will need to change the code, run npm run build again, and click the “Refresh” button (5).

Now you have your extension ready, just follow this official documentation and publish your extension to Chrome Web Store.

Thank you for reading this far. You could check the source code in Github or download the extension in Chrome Web Store, happy hacking!