The source code for this blog is available on GitHub.
Note
Top

Next.js Basics: Essential Knowledge for Every Developer

Cover Image for Next.js Basics: Essential Knowledge for Every Developer
Chen Han
Chen Han

CSS issue I came across in next.js

The CSS style is wrapped in a style JSX element, which is intended for adding styles to a React component. In order to apply this style to the entire document, you need to move the style element outside of the p element and add the global attribute to it.

<p className="my-5 font-normal">
  <style jsx global>{`
    button + button {
      margin-left: 10px;
    }
  `}</style>
   {product.Registered ? (
     <div className="mb-4">This product has been registered</div>
   ) : (
     <Button
       text="REGISTER"
       type="primary"
       className="block"
       disabled={!product.IsWeb && !invoiceImage}
       onClick={registerProduct}
       loading={isLoading}
       medium
     />
   )}
   {!isLoading &&
     (process.env.NODE_ENV === "development" ||
       product.Registered) && (
       <Button
         text="RETYPE"
         type="secondary"
         className="block max-w-[200px]"
         onClick={() => setProduct({})}
         medium
       />
     )}
 </p>

Here is how the revised code should look:

<style jsx global>{`
  button + button {
    margin-left: 10px;
  }
`}</style>
<p className="my-5 font-normal">
  {product.Registered ? (
    <div className="mb-4">This product has been registered</div>
  ) : (
    <Button
      text="REGISTER"
      type="primary"
      className="block"
      disabled={!product.IsWeb && !invoiceImage}
      onClick={registerProduct}
      loading={isLoading}
      medium
    />
  )}
  {!isLoading &&
    (process.env.NODE_ENV === "development" || product.Registered) && (
      <Button
        text="RETYPE"
        type="secondary"
        className="block max-w-[200px]"
        onClick={() => setProduct({})}
        medium
      />
    )}
</p>

With this change, the style should be applied to all buttons on the page that are immediately preceded by another button.

Do you know why cause this error when i upgrade to react@18

Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.

There could be several reasons why you might see this error when upgrading to React 18. Some possible causes include:

  1. Infinite loop: If you are using a component that calls setState inside a useEffect hook, and the useEffect hook does not have a dependency array, or one of the dependencies changes on every render, it can cause an infinite loop. This can lead to the "Maximum update depth exceeded" error, as the component will keep re-rendering indefinitely.
  2. Nested updates: If you are updating the state of a component from inside another component that is also updating its state, it can cause the "Maximum update depth exceeded" error. This can happen if the two components are nested in such a way that the inner component's update triggers an update in the outer component, which in turn triggers an update in the inner component, and so on.
  3. Unhandled errors: If an error is thrown inside a component that is not caught, it can cause the component to stop rendering and lead to the "Maximum update depth exceeded" error.

To troubleshoot this error, it can be helpful to look at the component or components that are causing the error and try to identify any potential issues with infinite loops, nested updates, or unhandled errors. It may also be helpful to review the React documentation and seek additional guidance or assistance from the React community.

Based on the code, it looks like this is a React component that is using several state management tools, including the useState hook, the useRecoilState and useRecoilValue hooks, and the useContext hook.

There are several useEffect hooks in the code, but it is not clear from the code snippet which of these hooks might be causing an infinite loop. To identify the source of the infinite loop, you may need to look at the logic inside these useEffect hooks and try to identify any potential issues with missing or incorrect dependencies, nested updates, or unhandled errors.

React Hydration Error

In general this issue is caused by using a specific library or application code that is relying on something that could differ between pre-rendering and the browser. An example of this is using window in a component's rendering.

An example:

function MyComponent() {
  // This condition depends on `window`. During the first render of the browser the `color` variable will be different
  const color = typeof window !== 'undefined' ? 'red' : 'blue'
  // As color is passed as a prop there is a mismatch between what was rendered server-side vs what was rendered in the first render
  return <h1 className={`title ${color}`}>Hello World!</h1>
}

How to fix it:

// In order to prevent the first render from being different you can use `useEffect` which is only executed in the browser and is executed during hydration
import { useEffect, useState } from 'react'
function MyComponent() {
  // The default value is 'blue', it will be used during pre-rendering and the first render in the browser (hydration)
  const [color, setColor] = useState('blue')
  // During hydration `useEffect` is called. `window` is available in `useEffect`. In this case because we know we're in the browser checking for window is not needed. If you need to read something from window that is fine.
  // By calling `setColor` in `useEffect` a render is triggered after hydrating, this causes the "browser specific" value to be available. In this case 'red'.
  useEffect(() => setColor('red'), [])
  // As color is a state passed as a prop there is no mismatch between what was rendered server-side vs what was rendered in the first render. After useEffect runs the color is set to 'red'
  return <h1 className={`title ${color}`}>Hello World!</h1>
}

Error: Hydration failed because the initial UI does not match what was rendered on the server.

The error "Hydration failed because the initial UI does not match what was rendered on the server" typically occurs when the client-side JavaScript in your Next.js application is attempting to hydrate the page, but the DOM structure of the page does not match the HTML that was rendered on the server. This can happen if the client-side JavaScript makes changes to the DOM that are not reflected in the server-rendered HTML.

One possible cause of this error in your code could be the use of the cart variable to conditionally render content on the page. If the cart variable is not available on the server when the page is rendered, the server-rendered HTML will not include the content that is being rendered based on the value of cart. If the value of cart changes on the client after the page is loaded, the client-side JavaScript will try to hydrate the page based on the new value of cart, but the server-rendered HTML will not match the DOM structure, resulting in the hydration error.

To fix this error, you will need to ensure that the content that is being rendered based on the value of cart is present in the server-rendered HTML, or you will need to use client-side rendering for this content. You could also consider using getServerSideProps to fetch the value of cart on the server and pass it to the

{ cart ? (
  &lt;div className=&quot;summary px-md pt-md&quot;&gt;
  &lt;/div&gt;
) : (
  &lt;div className=&quot;summary px-md pt-md&quot;&gt;
    &lt;div className=&quot;line-items&quot;&gt;
      &lt;p className=&quot;flex justify-between text-base font-normal&quot;&gt;
        &lt;span&gt;0 Item&lt;/span&gt;
        &lt;span&gt;-&lt;/span&gt;
      &lt;/p&gt;
      &lt;p className=&quot;flex justify-between text-base&quot;&gt;
        &lt;span&gt;Shipping&lt;/span&gt;
        &lt;span&gt;-&lt;/span&gt;
      &lt;/p&gt;
      &lt;p className=&quot;flex justify-between text-base font-normal&quot;&gt;
        &lt;span&gt;Tax&lt;/span&gt;
        &lt;span&gt;-&lt;/span&gt;
      &lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
)}

See more info here: https://nextjs.org/docs/messages/react-hydration-error

What is the difference between getServersideProps and getStaticProps

In a Next.js application, getServerSideProps and getStaticProps are functions that you can use to fetch data for your pages and pass the data to the client as props. Both functions can be used to fetch data from an API or from a database, and both functions can be used to pre-render pages on the server.

The main difference between getServerSideProps and getStaticProps is that getServerSideProps is called on every request to the page, while getStaticProps is called at build time to generate a static HTML file for the page.

Here are a few key points to consider when deciding which function to use:

  • getServerSideProps is suitable for pages that require data that is specific to the current request, or that need to be fetched on every request to the page. It is often used for pages that include dynamic data, such as pages that display data from a database that is filtered or sorted based on user input.

    getServerSideProps is often used for pages that require data that is specific to the current request, or that need to be fetched on every request to the page. It is a powerful tool for building server-rendered pages in Next.js, and can be used to fetch data from APIs or databases, or to perform other server-side operations.

  • getStaticProps is suitable for pages that do not require data that is specific to the current request, or that do not need to be fetched on every request to the page. It is often used for pages that include static data, such as pages that display data from a database that does not change frequently.

Can I put cart data inside getServerSideProps even getServerSideProps can be mutate

Yes, you can use getServerSideProps to fetch and pass data for the cart to the client, even if the data may change over time.

Here's how you can use getServerSideProps to fetch and pass cart data to the client:

  1. Define a getServerSideProps function in your page component that fetches the cart data from an API or other data source.
  2. In the getServerSideProps function, return an object with a props property that contains the cart data.
  3. In your page component, access the cart data as props.

For example:

export async function getServerSideProps(context) {
  const cart = await fetchCartData();

  return {
    props: {
      cart,
    },
  };
}

function MyPage(props) {
  const { cart } = props;
  // use the cart data in the component
}

You can update the cart data on the client by making API requests or by using other client-side techniques, such as using local storage or a client-side database. However, it's important to note that the cart data will not be persisted between requests unless you use a server-side technique to store the data, such as using a database or a server-side session.

I am so curious about if i want to server side the cart data in whole site. Do I need to write the same code in the entire files inside pages folder at getServerSideProps?

If you want to server-side render the cart data on every page of your Next.js application, you will need to make sure that the cart data is available to every page component that needs it. One way to do this is to define a getServerSideProps function in each page component that needs the cart data, and have the function fetch the data from an API or other data source.

For example, if you have a page component called MyPage that needs the cart data, you could define a getServerSideProps function in the component like this:

export async function getServerSideProps(context) {
  const cart = await fetchCartData();

  return {
    props: {
      cart,
    },
  };
}

If you have multiple page components that need the cart data, you will need to define a getServerSideProps function in each of them. This can be a bit repetitive, but it ensures that the cart data is fetched and passed to the page component as props on every request to the page.

Alternatively, you could consider using a custom server or a higher-level API to fetch the cart data and make it available to all of your page components. This would allow you to centralize the logic for fetching the cart data and avoid having to duplicate the code in multiple page components.

A custom server in a Next.js application is a Node.js server that is set up and configured by the developer to handle incoming requests to the application. It allows you to define custom routes and logic for handling requests, and to integrate your Next.js application with other back-end systems and APIs.

You can use a custom server in a Next.js application to handle a variety of tasks, such as:

  • Fetching data from APIs or databases
  • Authenticating users and managing sessions
  • Handling file uploads and downloads
  • Caching data or handling rate limiting
  • Performing server-side rendering of pages or components

To set up a custom server in a Next.js application, you will need to create a server.js file in the root directory of your project and define a Node.js server using a framework like Express.js. You can then configure the server to handle incoming requests and to integrate with your Next.js application.

Here's a simple example of a custom server in a Next.js application:

const express = require('express');
const next = require('next');

const port = process.env.PORT || 3000;
const dev = process.env.NODE_ENV !== 'production';
const app = next({ dev });
const handle = app.getRequestHandler();

app.prepare().then(() => {
  const server = express();

  server.get('/api/data', (req, res) => {
    res.json({ data: 'Hello, world!' });
  });

  server.get('*', (req, res) => {
    return handle(req, res);
  });

  server.listen(port, (err) => {
    if (err) throw err;
    console.log(`> Ready on http://localhost:${port}`);
  });
});

In this example, the custom server is set up to handle incoming requests to the /api/data route and to return a JSON response. It also forwards all other requests to the Next.js application using the handle function provided by Next.js.

Why sometimes my project will auto generate tsconfig.json and next-env.d.ts file

Sometimes, my project will automatically generate these files when I use a tool or library that requires TypeScript. The "tsconfig.json" file is used to configure the TypeScript compiler, and the "next-env.d.ts" file is used to provide type definitions for environment variables in a Next.js project.

jsconfig.json && current tsconfig.json file

tsconfig.json specifies the TypeScript compiler options. The options defined in the file are used to compile the TypeScript code into JavaScript code.

jsconfig.json was an outdated file in the project, while the current file being used is tsconfig.json.

tsconfig.json specifies the TypeScript compiler options. The options defined in the file are used to compile the TypeScript code into JavaScript code.

The compilerOptions key specifies various options for the TypeScript compiler. The options include:

  • target: The language version of the generated JavaScript code. In this case, the value is es2016.
  • skipLibCheck: A flag that indicates whether to skip type checking of declared library files.
  • lib: Specifies the default library files to include in the compilation.
  • allowJs: A flag that allows JavaScript files to be part of the project.
  • noEmit: A flag that indicates whether to emit the output.
  • incremental: A flag that indicates whether to use incremental compilation.
  • moduleResolution: The strategy for resolving module names.
  • resolveJsonModule: A flag that indicates whether to support resolving JSON modules.
  • isolatedModules: A flag that indicates whether the code should be treated as an isolated module.
  • jsx: Specifies how to handle JSX syntax in the code.
  • baseUrl: Specifies the base directory to resolve non-relative module names.
  • paths: Specifies mapping of module names to locations relative to the baseUrl.

The include key lists the files that should be included in the compilation. The exclude key lists the files that should be excluded from the compilation.

JSX

The jsx option in a TypeScript configuration file specifies how TypeScript should handle JSX syntax in your code.

  • jsx: "preserve" means that TypeScript will not transform the JSX syntax in your code and it will preserve it as is. This means that the transformed code will contain JSX syntax which is only supported by some JavaScript runtimes, such as React.
  • jsx: "react" means that TypeScript will compile the JSX syntax in your code to JavaScript code that can be run in any JavaScript runtime. The transformed code will not contain any JSX syntax and it will be equivalent to calls to React functions such as React.createElement().

In general, you would use jsx: "react" when your code will run in a JavaScript environment that doesn't support JSX syntax, and you would use jsx: "preserve" when your code will run in a JavaScript environment that supports JSX syntax.

// outdated jsconfig.json file
{
	"compilerOptions": {
    "jsx": "react",
		"baseUrl": ".",
		"paths": {
			"@/components/*": ["components/*"],
			"@/api/*": ["pages/api/*"],
			"@/icons/*": ["components/icons/*"],
			"@/atoms/*": ["recoil/*"],
			"@/selectors/*": ["recoil/selectors/*"],
			"@/auth/*": ["pages/auth/*"],
			"@/lib/*": ["lib/*"],
			"@/config/*": ["util/config/*"],
			"@/util/*": ["util/*"],
			"@/pages/*": ["pages/*"],
			"@/hooks/*": ["hooks/*"],
			"@/reactContext/*": ["util/reactContext/*"],
			"@/*": ["./*"]
		},
		"module": "ESNext",
		"target": "esnext"
	}
}

// current tsconfig.json file
{
  "compilerOptions": {
    "target": "es2016",
    "skipLibCheck": true,
    "lib": ["dom", "dom.iterable", "esnext"],
    "allowJs": true,
    "noEmit": true,
    "incremental": true,
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "jsx": "preserve",
    "baseUrl": ".",
    "paths": {
      "@/components/*": ["components/*"],
      "@/api/*": ["pages/api/*"],
      "@/icons/*": ["components/icons/*"],
      "@/atoms/*": ["recoil/*"],
      "@/selectors/*": ["recoil/selectors/*"],
      "@/auth/*": ["pages/auth/*"],
      "@/lib/*": ["lib/*"],
      "@/config/*": ["utils/config/*"],
      "@/utils/*": ["utils/*"],
      "@/util/*": ["utils/*"],
      "@/pages/*": ["pages/*"],
      "@/hooks/*": ["hooks/*"],
      "@/reactContext/*": ["utils/reactContext/*"],
      "@/*": ["./*"]
    }
  },
  "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "types/*.d.ts"],
  "exclude": ["node_modules"]
}

Disabled console.log in next.js file

In the code, there is a conditional statement that checks the value of process.env.VERCEL_ENV. If it is equal to 'production', then the global console.log function is overwritten with an anonymous function that does nothing.

The purpose of this code is to prevent console.log statements from being executed in the production environment, while allowing them to work as normal in other environments. By overwriting the console.log function in this way, any calls to console.log in the code will simply do nothing in production, without having to remove the calls themselves.

module.exports = withAxiom({
  webpack: (config, options) => {
    config.resolve = {
      ...config.resolve,
      fallback: {
        fs: false,
      },
    };

    if(process.env.VERCEL_ENV === 'production') {
      global.console.log = () => {};
    }

    console.log("VERCEL_ENV:", process.env.VERCEL_ENV);

    config.plugins.push(
      new webpack.ProvidePlugin({
        $project: "$project.ts",
        util: "$utility.ts",
      })
    );

    return config;
  },
});
© 2024 WOOTHINK. All Rights Reserved.
Site MapTerms and ConditionsPrivacy PolicyCookie Policy