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 buildoutput: 'export',reactStrictMode: true,images: {unoptimized: true},// access by process.env.customKeyenv: {customKey: 'my-value'},// base path and link /docs/about => /aboutbasePath: '/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 autoprefixernpx 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: stringchildren: 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 } = propsreturn (<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>)}