Introduction#

GitHub this note shows

  • From HTML to ReactJS component
  • How NextJS works?
  • The App and Document components

From HTML to React#

  • HTML => DOM => UI
  • the browser receives a HTML file with text and js
  • the browser reads the HTML and construct DOM (Document Object Model)
  • javascript listen to user events and manipulate the DOM objects
<html>
<body>
<div id="app"></div>
<script src="https://unpkg.com/react@17/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script type="text/jsx">
const app = document.getElementById("app")
function Header({ title }) {
return <h1>{title ? title : "Default title"}</h1>
}
function HomePage() {
const names = ["Ada Lovelace", "Grace Hopper", "Margaret Hamilton"]
const [likes, setLikes] = React.useState(0)
function handleClick() {
setLikes(likes + 1)
}
return (
<div>
<Header title="Develop. Preview. Ship. 🚀" />
<ul>
{names.map((name) => (
<li key={name}>{name}</li>
))}
</ul>
<button onClick={handleClick}>Like ({likes})</button>
</div>
)
}
ReactDOM.render(<HomePage />, app)
</script>
</body>
</html>

Finally, with nextjs the code becomes

import { useState } from 'react'
function Header({ title }) {
return <h1>{title ? title : 'Default title'}</h1>
}
export default function HomePage() {
const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton']
const [likes, setLikes] = useState(0)
function handleClick() {
setLikes(likes + 1)
}
return (
<div>
<Header title="Develop. Preview. Ship. 🚀" />
<ul>
{names.map(name => (
<li key={name}>{name}</li>
))}
</ul>
<button onClick={handleClick}>Like ({likes})</button>
</div>
)
}

How NextJS Work#

Create a new nextjs project, and then select options such as app, src, eslint integration

npx create-next-app@latest . --typescript

Project structure

|--pages
|--_app.tsx
|--_document.tsx
|--index.tsx
|--styles
|--global.css
|--public
|--favicon.ico
|--vercel.svg
|--next.config.js
|--package.json
|--tailwind.config.ts
|--tsconfig.json
|--.eslintrc.json
|--prettier.config.js
|--postcss.config.js

How Tailwind works with NextJS?

  • tailwind.config.js instruct how tailwind complie things (html, input.css)
  • the tailwind utilities are imported into global.css

Nextjs uses the App component to initialize pages, so you can

  • Persist layouts between page changes
  • Keeping state when navigating pages
  • Add global CSS, metadata such as head (next/head)

The Document component

  • update the html and body tags used to render a Page
  • the Head component used here should only be used for any head code that is common for all pages
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}

What is next.config.js?

  • used by the Next.js server and build phases
  • add environment variables
  • the base path and link
  • rewrite path and parameters, path matching
  • redirect
/** @type {import('next').NextConfig} */
const nextConfig = {
// export static when npm run build
output: 'export',
reactStrictMode: true,
images: {
unoptimized: true
},
// access by process.env.customKey
env: {
customKey: 'my-value'
},
// base path and link /docs/about => /about
basePath: '/docs'
// rewrite path and parameters
}
module.exports = nextConfig

Typescript and tsconfig.json, it is possible to change module path

"baseUrl": ".",
"paths": {
"components/*": ["src/components/*"],
"data/*": ["src/data/*"],
"hooks/*": ["src/hooks/*"],
"utils/*": ["src/utils/*"],
"contentlayer/generated": ["./.contentlayer/generated"]
},

NextJS Manual Setup#

There are two ways to setup a NextJS project

  • Using npx create-next-app@latest
  • Manual setup with npm install packages and create project structure

Create a project nextjs-shared-layout, then install

npm install next@latest react@latest react-dom@latest

Update package.json

{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
}

Then install Tailwind

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Please ensure the content of tailwind.config.js as the following

/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./layouts/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}'
],
theme: {
extend: {
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic':
'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))'
}
}
},
plugins: []
}

Create project structure

|--layouts
|--container.tsx
|--pages
|--_app.tsx
|--_document.tsx
|--index.tsx
|--styles
|--globals.css
|--next.config.js
|--package.json
|--postcss.config.js
|--tailwind.config.js
|--tsconfig.json

When initial running npm run dev, it will install typescript packages also, the final package.json look like this

{
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"next": "^13.4.2",
"package.json": "^2.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/node": "20.2.0",
"@types/react": "18.2.6",
"autoprefixer": "^10.4.14",
"eslint": "8.40.0",
"eslint-config-next": "13.4.2",
"postcss": "^8.4.23",
"tailwindcss": "^3.3.2",
"typescript": "5.0.4"
}
}

Shared Layout#

There are at least two ways to develop shared layouts between pages

  • Old way with children as JSX.Element
  • New way with NextJS App Router HERE
  • Please check React.ReactNode versus JSX.Element

With the old way, we create a project structure as above, and create two layouts as below.

import React from 'react'
interface PageContainerProps {
name: string
children: React.ReactNode
}
export const PageContainer = ({ children }: { children: JSX.Element }) => {
// const { name, children } = props;
return (
<div className="max-w-5xl bg-green-200 mx-auto min-h-screen">
{children}
</div>
)
}
export const SecondContainer = (props: PageContainerProps) => {
const { name, children } = props
return (
<div>
<div className="bg-green-400">
<div className="max-w-5xl mx-auto flex justify-between py-5 px-5">
<text className="cursor-pointer">Entest</text>
<ul className="hidden md:flex gap-x-3">
<li className="bg-white hover:bg-green-600 hover:text-white px-3 py-1 rounded-sm">
<a href="#" target="_blank">
About Me
</a>
</li>
</ul>
</div>
</div>
<div className="max-w-5xl mx-auto bg-slate-200">{children}</div>
<div className="bg-gray-800 py-5 flex justify-center">
<text className="text-white">Entest</text>
</div>
</div>
)
}

Then we can use the layout in others pages or in app.tsx. Please ensure that the children is JSX.Element. React.ReactNode is much wider (can be text, JSX.Element, nul, etc).

import { PageContainer, SecondContainer } from '@/layouts/container'
const Nav = () => {
return <h1>Hello Hai Minh</h1>
}
export default function Home() {
return (
<SecondContainer name="hai">
<Nav></Nav>
</SecondContainer>
)
}

Reference#