reference: Doc
- Init
- Routing
- Typescript tsconfig alias
- Font
- Server vs. Client Components
- Next Themes
- Next Internationalization
- Storybook
reference: Doc
npx create-next-app@latest
Result:
What is your project named? my-app
Would you like to use TypeScript? Yes
Would you like to use ESLint? Yes
Would you like to use Tailwind CSS? Yes
Would you like to use `src/` directory? Yes
Would you like to use App Router? (recommended) Yes
Would you like to customize the default import alias (@/*)? Yes
What import alias would you like configured? @/*
reference: Doc
-
Page Doc
-
Layout Doc
-
Template Doc
-
Group Route Doc
-
Dynamic Route Doc
-
Multi Segments Doc
-
-
Private Folder Doc
-
Not Found Doc
-
Meta Data Doc
{
"compilerOptions": {
"paths": {
"@/components/*": ["./src/app/_components/*"],
"@/layouts/*": ["./src/app/_layouts/*"]
}
}
}
reference: Doc
In src/app/layout.tsx
import localFont from 'next/font/local'
// Font files can be colocated inside of `app`
const myFont = localFont({
src: './my-font.woff2',
display: 'swap',
variable: '--my-font',
})
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en" className={myFont.variable}>
<body>{children}</body>
</html>
)
}
reference: Doc
In tailwind.config.ts
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./src/app/**/*.{js,ts,jsx,tsx}',
],
theme: {
fontFamily: {
sans: ['var(--my-font)'],
},
},
plugins: [],
}
YouTube Video with example:
reference: Doc
npm install next-themes
with tailwind: Doc
In tailwind.config.ts
module.exports = {
darkMode: 'class'
}
In src/app/_layouts/ProvidersLayout/ProvidersLayout.tsx
'use client'
import { ThemeProvider } from 'next-themes'
import { FC, PropsWithChildren } from 'react'
const ProviderLayout: FC<PropsWithChildren> = ({ children }) => {
return (
<ThemeProvider enableSystem={false} attribute="class">
{children}
</ThemeProvider>
)
}
export default ProviderLayout
In src/app/_components/ThemeChanger/ThemeChanger.tsx
'use client'
import { useTheme } from 'next-themes'
import { FC, useEffect, useState } from 'react'
const ThemeChanger: FC = () => {
const { theme, setTheme } = useTheme()
{/** Just Run In Client */}
const [isInClient, setIsInClient] = useState(false)
useEffect(() => {
setIsInClient(true)
}, [])
const toggle = () => {
setTheme(theme === 'light' ? 'dark' : 'light')
}
if (!isInClient) return null
return (
<div>
<button onClick={toggle}>
{theme}
</button>
</div>
)
}
export default ThemeChanger
In src/app/layout.tsx
import ProviderLayout from '@layouts/ProvidersLayout/ProvidersLayout' // <== Add This
import ThemeChanger from '@components/ThemeChanger/ThemeChanger' // <== Add This
// ...
const RootLayout: FC = () => {
// ...
return (
<html className={myFont.variable}>
<body>
<ProviderLayout> {/** <== Add This */}
<ThemeChanger /> {/** <== Add This */}
{children}
</ProviderLayout> {/** <== Add This */}
</body>
</html>
)
}
export default RootLayout
reference: Doc
npm install next-intl@3.0.0-beta.19
In src/app/_messages/en/general.ts
const message = {
title: 'Hello world!',
hello: 'Hello {name}!',
cardinal:
'You have {count, plural, =0 {no followers yet} =1 {one follower} other {# followers}}.',
richText: 'This is <important><very>very</very> important</important>',
}
export default message
In src/app/_messages/fa/general.ts
const message = {
title: 'سلام دنیا!',
hello: 'سلام {name}!',
cardinal:
'شما {count, plural, =0 {هیج فالوری ندارید} =1 {فقط یک فالور دارید} other {# فالور دارید}}.',
richText: 'این <important><very>خیلی</very> مهمه</important>',
}
export default message
In src/app/_messages/en.ts
export { default as general } from './en/general'
In src/app/_messages/fa.ts
export { default as general } from './fa/general'
In i18n.ts
import { getRequestConfig } from 'next-intl/server'
export default getRequestConfig(async ({ locale }) => {
return {
messages: await import(`./src/app/_messages/${locale}.ts`),
}
})
In next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {}
const withNextIntl = require('next-intl/plugin')('./i18n.ts')
module.exports = withNextIntl(nextConfig)
In src/middleware.ts
import createMiddleware from 'next-intl/middleware'
export default createMiddleware({
locales: ['fa', 'en'],
defaultLocale: 'fa',
})
export const config = {
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)'],
}
In src/app/_components/LanguageChanger/LanguageChanger.tsx
'use client'
import { useLocale } from 'next-intl'
import { usePathname, useRouter } from 'next-intl/client'
import { FC } from 'react'
const LanguageChanger: FC = () => {
const router = useRouter()
const pathname = usePathname()
const locale = useLocale()
const toggle = () => {
router.replace(pathname, { locale: locale === 'en' ? 'fa' : 'en' })
}
return (
<div>
<button onClick={toggle}>{locale === 'en' ? 'English' : 'فارسی'}</button>
</div>
)
}
export default LanguageChanger
Move layout.tsx
and page.tsx
from src/app
to (routes)/[locale]/
In src/app/(routes)/[locale]/layout.tsx
// ...
import ThemeChanger from '@components/LanguageChanger/LanguageChanger' // <== Add This
import { FC, PropsWithChildren } from 'react' // <== Edit This
import { notFound } from 'next/navigation' // <== Add This
const locales = ['fa', 'en'] // <== Add This
// ...
interface IRootLayoutProps extends PropsWithChildren<{ params: { locale: string } }> {} // <== Add This
const RootLayout: FC<IRootLayoutProps> = ({ children, params: { locale } }) => { // <== Edit This
// Add This
if (!locales.includes(locale)) notFound()
const isRtlLang = locale === 'fa'
return (
<html lang={locale} className={myFont.variable}> {/** <== Edit This */}
<body dir={isRtlLang ? 'rtl' : 'ltr'}> {/** <== Edit This */}
<ProviderLayout>
<LanguageChanger /> {/** <== Add This */}
<ThemeChanger />
{children}
</ProviderLayout>
</body>
</html>
)
}
export default RootLayout
In src/app/(routes)/[locale]/lang/page.tsx
import { useTranslations } from 'next-intl'
import { FC } from 'react'
const LangPage: FC = () => {
const t = useTranslations()
const tGeneral = useTranslations('general')
return (
<>
<p>{t('general.title')}</p>
<p>{tGeneral('title')}</p>
<p>{tGeneral('hello', { name: 'Masoud' })}</p>
<p>{tGeneral('cardinal', { count: 0 })}</p>
<p>{tGeneral('cardinal', { count: 1 })}</p>
<p>{tGeneral('cardinal', { count: 3580 })}</p>
<p>
{tGeneral.rich('richText', {
important: (chunks) => <b>{chunks}</b>,
very: (chunks) => <i>{chunks}</i>,
})}
</p>
</>
)
}
export default LangPage
reference: Doc
reference: Doc
example: src/app/_components/LanguageChanger/LanguageChanger.tsx
---> TODO <---