Skip to content

Next.js

Created: 2020-03-23 13:29:57 -0700 Modified: 2023-04-18 17:41:31 -0700

  • Starting a new project with pnpm: simply type “pnpm create next-app”. It’ll prompt you for everything.
  • Next.js is a framework to create single-page web applications with JavaScript and React that handles routing, server-side rendering, code-splitting, and API routes.
  • They have a getting-started guide here.
  • They use Webpack’s hot-module-replacement feature automatically, so you just have to save files, not reload the webpage.
  • There’s an App component to initialize pages (reference). It lets you keep state when navigating pages, add global CSS, etc.
  • If you find yourself with more than a single plugin, use next-compose-plugins so that you don’t have to fight the syntax of composition.
  • There’s a Document component for any sort of <head>, <script>, <meta>, or <body> elements that you may need.
    • If you just need a <script> tag on one page, you can use the Head element. There are a couple of ways to do this, but in general, you use dangerouslySetInnerHTML (reference). Someone on GitHub suggested another way of doing it that looks more React-y (reference)
    • If you’re doing this for open-graph, SEO, page titles, etc., you may want to consider using next-seo.
    • For <html lang=“en”/>, you’d put this into Document (reference)
  • They do client-side routing via <Link>. You don’t need to manage history by yourself.
  • At least on Windows, after installing more Node modules (e.g. something like Lodash), you should restart the dev server.
  • If you like dotenv (or dotenv-load) for configuring environment variables, you may like next-env.
    • After using next-env, it’ll automatically pull in the production version “.env.production.local” when you do “next build”, so if you don’t want that:
      • Easy way: specify that you want the development version:
const nextEnv = require('next-env');
const dotenvLoad = require('dotenv-load');
dotenvLoad('development.local');
const withNextEnv = nextEnv();
  • Hard way: copy .env.production.local to .env.production.local.old and overwrite the original

  • Their folder structure:

    • pages - this contains static routes, e.g. pages/about.js means you can navigate to localhost:3000/about
      • If you want a path like localhost:3000/p/about, then you’d make that file at “pages/p/about.js”
      • I think it’s strongly advised for you to make all page names completely lowercase, that way you never run into any weird casing issues (I hit some problems here myself and came to this conclusion).
    • pages/api - this is for any API routes
    • public - any static assets, e.g. robots.txt, the favicon, etc.
    • Beyond that, every other directory can be named however you want, but here are some conventions that Next.js uses:
      • components - this is for shared components
  • Middleware is supported

  • This is how you can fetch dynamic content for the props of a component before the component renders for the first time. However, I don’t know if this on its own is a good method because delays in asynchronous calls can lead to a bad user experience. For server-side rendering, I assume it’s great and probably even necessary since the client isn’t going to do that fetching itself.
  • Restrictions
    • It can only be added to the default exported component
    • It has to work on both the client and server since it’s called in both environments.
  • By default, the getting-started guide has the fetch run on the server for a static page, then the client just gets the rendered page without having to fetch anything itself. However, for the dynamic route, the fetch happened on the client because the routing is on the client without making a request to the server. If you were to visit the post page directly by clicking a full URL (e.g. http://localhost:3000/p/975) then the server would fetch it.
  • You need a next.config.js to control what’s exported (click the links on the left to find a specific topic, e.g. exportPathMap). By default, Next.js generates a map for you using pages inside the “pages” directory.
  • ”next build” and “next export” will produce “out/index.html” (among other files) that you can serve statically.
  • The exportPathMap is what controls which routes will be available when directly pathing to that page. E.g. if you only make your index available, then your whole app will still work, but you’ll have to navigate to “example.com” and not “example.com/posts/foo”.
    • Any path that you export will generate a corresponding HTML file in the “out” folder. The HTML content will include any prerendered content from getInitialProps, and you’ll also see a <script> tag linking to something like ”/_next/static/YzFWdLaFhKB810zY9Ltds/pages/show/%5Bid%5D.js” that contains your React code that will run.
    • It’s not possible to use wildcards/regexes because there’s no server to handle the wildcard matching (since it’s all static content).
  • Pages are case-sensitive in the exportPathMap whether or not you specify them. However, this can apparently vary based on development (“npx next”) vs. production (i.e. statically exported) (reference). I ran into a bunch of issues with this on my Windows machine.
  • Even if you’re just going to produce a bunch of static content, Next.js can still provide some value for you.
    • It’s a nice development environment since Webpack and its features (primarily hot-module reloading and code splitting) are set up for you.
    • Routing is handled for you.
  • When uploading this content to a server with an FTP program, don’t skip uploading files if they’re the same size as what the server already has. This is because there’s a new hash generated in out/_next every time, but the size of that hash doesn’t change, so if you don’t upload a new file when the size is the same, then it won’t get the new hash.

I follow these steps:

  • Set “distDir” to “./dist” in my next.config.js
  • Turn off the dev server first so that it doesn’t produce unnecessary files
  • Delete “dist” and “out”
    • Without doing this, you end up with many extra files from previous builds
  • $ next build
  • $ next export
  • Test with “http-server ./out” to make sure everything looks fine
  • Deploy from the “out” folder

(kw: homepagerizer since I forgot how to deploy last time I was looking for this)

This is SUPER easy.

You basically just go to https://vercel.com/#get-started and follow the instructions. My code was on GitHub, so I connected GitHub, clicked a few buttons, configured almost nothing (since I already had “next build” as a script in my package.json), and it deployed my site.

It automatically keeps your site up-to-date as you push to GitHub too.

If you need secrets, I think you can add them through the UI, but you can also use their CLI (“vercel secrets add key value”). ”@” signs are only needed in vercel.json → env (reference), not on the CLI.

  • Firebase gives you <script> tags that they tell you to put into the HTML directly. On Next.js, you can just do “npm install firebase”.
  • For a simple routing example using a query string, check the getting-started guide. They import the useRouter Hook, then they do
router = useRouter();
router.query.title; // this refers to the ?title=FOO query parameter
  • Dynamic routes are supported by square brackets. At the time of writing (March 24th, 2020), you can only have the entire page’s name be dynamic, not just a part of it like “post-[id].js”.
    • After making something like “[id].js”, the “id” becomes the query parameter passed through the router
    • When using dynamic routes, the “href” property of <Link> should always point to the raw path, then you should always add an “as” property with the formatted path, e.g. <Link href=“/p/[id]” as={/p/${id}}>
      • If you were to instead do this: <Link href={/p/${id}} as={/p/${id}}>
      • …it would still route you in the end, but you’d get an error in the process that looks like this:

GET http://localhost:3000/_next/static/development/pages/p/deploy-nextjs.js net::ERR_ABORTED 404 (Not Found)

  • My limited understanding so far is that by not using “href” and “as” properly, you could either end up with errors or messing up prefetching and history.

JavaScript doesn’t seem to be working after static export

Section titled JavaScript doesn’t seem to be working after static export

I ran into an issue where none of the buttons on my page would work when I statically exported (but they would work in development). It took a long time to debug, and the problem ended up being TailwindCSS. The short story is that I needed to move this line from pages/index.js into pages/_app.js:

import "tailwindcss/tailwind.css";

I actually didn’t even have an _app.js, so I had to make one:

import "tailwindcss/tailwind.css";
import "rc-tooltip/assets/bootstrap.css";
import "react-toastify/dist/ReactToastify.css";
import "rc-checkbox/assets/index.css";
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}

This is because custom click handlers get added to the <a> element after statically exporting, and those are somehow stopping the click from working. This is actually just because your exportPathMap is wrong. For example, I had “pages/Courses.js” on my filesystem, but I was linking to it like this:

<Link href="/courses" as={`/courses`}>

It wasn’t until I changed it to a capital “C” that it worked (so that it matches my filesystem):

<Link href="/Courses" as={`/courses`}>

Given what’s said at the reference link, the PR may eventually get merged, but I doubt it since it’s been about 7 months since the PR was ready and it still hasn’t been merged. My workaround was to make all of my filenames lowercase since otherwise, my deploys got absolutely destroyed by all of the casing-related problems.

(note: the generic error is just a 404 for any FOO.EXT.JS where “EXT” is not “.js” already)

This happens when you statically export a <Link> tag that should have been an <a> tag:

BAD:

<Link href="/licenses.txt" as={`/licenses.txt`}>
<a>Software licenses</a>
</Link>

GOOD:

<a href="/licenses.txt">Software licenses</a>

I believe it’s because Next.js is trying to pre-fetch the content since it assumes that <Link>s are to pages.

  • It’s only called in pages, not components (reference)
  • You may have put a getInitialProps into _app.js (reference)

Woes accessing cookies or the query string

Section titled Woes accessing cookies or the query string

It’s happened to me in the past with both of these where I knew that cookies or query parameters were present, and I was even able to log them, but my site wasn’t rendering properly in testing. This is due to how server-side rendering works. When in development, the server will render your page for the first time, so it’s going to use the values it has set. Thus, you should read your cookies and query parameters from getInitialProps.