This is the first post in a series I'll be writing about the creation of my new webapp Readlist. Readlist is an app that lets you earn money by creating and sharing book lists. Check it out here --> https://readlist.io It's free!
If you're new to ReactJS like me, you probably created your first app using create-react-app
(CRA). CRA is a super easy, beginner-friendly way to get started with React, but it comes with a big caveat: your app will most likely be "client-side only", which means that search engines such as Google and social sites such as Facebook and Twitter probably won't read your metatags, and therefore can't index or create social cards for your pages.
If your web-app exists behind an auth barrier and you don't intend for the uninitiated to see it, then a CRA is probably fine. But what happens if you have a lot of outward-facing, publicly-accessible pages that desperately need to be SEO-friendly and read by the social networks?
Possible Solution: Use react-helmet
.
React-helmet is a library that supposedly lets you append metatags to your head tag, and therefore be more easily indexed. I tried react-helmet in my app, but after several days of debugging, it just wasn't working. Therefore, I decided to take a deep dive and start exploring something called server-side rendering (SSR) that I'd seen people talking about.
Real Solution: Use Server-Side Rendering (NextJS)
Server-side rendering, like it sounds, is a way to render your page on the server before serving it to the end-user. Faster loading times is one of the main benefits, but for me, the real benefit is loading in data before the render, which allows me to customize HTML metatags on the page before it gets indexed.
After some research, I discovered two frameworks for SSR: Gatsby and NextJS. Gatsby is optimized for delivering static pages, but Readlist has lots of content that will update frequently, so I opted to use NextJS.
With companies like TikTok, Nike, and Twitch using NextJS, you can be sure that NextJS is a capable framework for your SSR needs.
In the remainder of this post, I'd like to go through 5 difficulties I encountered while converting my CRA app to NextJS, and how I solved them.
1. Handling Global State
Global state is a must-have for a single-page app (SPA) like Readlist, which handles authentication on initial page load.
In a normal CRA app, it's easy to wrap your main App component in a state provider like Redux or the React Context Provider. It's simple to do in NextJS as well, but it took me several times reading through the excellent NextJS docs to discover the proper way.
The trick is to create an _app.js
folder in the pages/
directory and extend the App class. Then you can wrap the Component class (a NextJS class) with your State provider. Not bad!
2. Routing
Routing your pages in NextJS is a bit more complicated than in a CRA app. To convert your CRA app into a NextJS app, you need to create a pages/
folder in the root directory and place all of your app pages there.
Since NextJS allows for both server-side routing and client-side routing, it also uses a special Router component found in next/router.
The NextJS router has its own custom Link component that handles client-side routing, but the syntax is a bit tricky. Link takes two properties: href
and as
. If you have a simple link like "about", then you can just use href
. However, if you need a more complicated link like /user/coderdannn, you will need both href
and as
. In this case, href
takes a template of your URL, with the dynamic part encased in []. So in the above case, it would be href="/user/[userID]"
. Then in as
, you actually write the real path: /user/${userID}
. It took me several hours of trial and error to finally get that working!
3. getInitialProps
getInitialProps is part of the magic of NextJS -- where the real server-side rendering really happens. NextJS pages are statically optimized by default, and therefore not server-side rendered. However, on those pages you want to contain metatags and be indexed or shared (such as user profile pages, or book lists) you will need to render them server-side.
getInitialProps is the NextJS solution to that. getInitialProps can be called on the default export component of your .js or .tsx file, and it returns an object that, when finished, gets sent to the props of your default export. The page will not finish loading until that fetch has completed. For example, if I want to fetch a user object to display in my User component, I can call getInitialProps, await fetch my object, and then return {userObject:fetchedObject}
. It's that easy!
Note that if you're using Firebase like I am, if you use getInitialProps to fetch your data, you cannot use the onSnapshot method to listen to changes in your objects. Which is kind of a bummer.
4. Environment Variables
Environment variables work basically the same way as in a CRA app, except you need to also declare them in the next.config.js file within an env:{}
object. Then you can call process.env.VAR_NAME
as you would in a CRA app. Big thanks to NextJS lead developer @timneutkens
for pointing this out to me. I have to say that the NextJS community is super friendly and always willing to help out!
5. CSS and Dynamic Loading
NextJS by default enables code-splitting, which splits your bundles into multiple pieces. This is great for server-side rendering because it means your user has less data to consume before becoming interactive. In addition, you can also dynamically require components using next/dynamic, which will split that component into its own separate bundle.
One area to be careful is in your external css modules. For Readlist, I used a lot of global external CSS modules, which turned out to be pretty inefficient in NextJS because the entire CSS module had to load before it can render the page. NextJS recommends you use local CSS whenever possible, and it uses a special styled-jsx
library to achieve that. One of my biggest speed optimization gains was copying all of that external CSS into the files that consumed it.
Further Thoughts
Converting my CRA app into a NextJS SSR was definitely a painful task, but then again, so is learning anything new. Thanks to all the great community members for writing great docs, and also for creating dozens of examples of NextJS on Github.
My app performance still isn't perfect yet, as you can see above, but by converting to SSR, I achieved my main goal: allowing users to share their lists to Twitter and Facebook. High five!
I'm active on Twitter so if you'd like to chat about developing, startups, books, living in Asia, or anything else, feel free to DM me! @coderdannn
Top comments (7)
Hey Daniel, thanks for this amazing article.
me and my co-worker are about to convert a client side react website into a NextJS one and I wanted to ask you based on your experience how much time did it take you to totally convert your website?
again thanks this will be of great help to us, wish you the best of luck.
Hi Hazem,
All in all I'd say it took about 2 days. The site wasn't very big so most of that time was spent researching best practices and workarounds.
If you need any dedicated assistance feel free to send me an e-mail at coderdannn@gmail.com and we can chat more =)
Hello Daniel, nice article, had it in my bookmarks and proved to be very useful.
I started using Next.JS to go SSR and it's great with Firebase. However, after releasing my changes, I noticed that Google can't index my site anymore, bailing out with an
Internal Error
if the user-agent is set togooglebot
.I tried setting the user-agent on your site too to
googlebot
and I am seeing the same behaviour. Are you aware of this issue? I'd really like to fix it for my site, it's deemed to be SEO friendly.Thanks,
Attila
Hi Attila, thanks for your question. I haven't specifically encountered that issue, but I highly recommend you contact the NextJS team about this issue. Tim Neutkens is the lead dev and he's very responsive on Twitter. His handle is @timneutkens .
Thanks for reading the article! If you can't get help from the NextJS team I'd be glad to help you out.
Hey Daniel, thanks for this right up , it really helped me a lot . But I am facing error-Module not found: Can't resolve 'react-native', while I tried moving my react app to nextjs app. Could you please help me with this. Thanks!
Great read! I wish there was an updated version of this for 2020
Some comments may only be visible to logged-in visitors. Sign in to view all comments.