├── basics
├── demo
│ ├── .nvmrc
│ ├── README.md
│ ├── public
│ │ ├── favicon.ico
│ │ └── images
│ │ │ └── profile.jpg
│ ├── pages
│ │ ├── api
│ │ │ └── hello.js
│ │ ├── _app.js
│ │ ├── posts
│ │ │ └── [id].js
│ │ └── index.js
│ ├── components
│ │ ├── date.js
│ │ ├── layout.module.css
│ │ └── layout.js
│ ├── .gitignore
│ ├── package.json
│ ├── styles
│ │ ├── global.css
│ │ └── utils.module.css
│ ├── posts
│ │ ├── pre-rendering.md
│ │ └── ssg-ssr.md
│ └── lib
│ │ └── posts.js
├── basics-final
│ ├── .nvmrc
│ ├── README.md
│ ├── pages
│ │ ├── api
│ │ │ └── hello.js
│ │ ├── _app.js
│ │ ├── posts
│ │ │ └── [id].js
│ │ └── index.js
│ ├── public
│ │ ├── favicon.ico
│ │ └── images
│ │ │ └── profile.jpg
│ ├── components
│ │ ├── date.js
│ │ ├── layout.module.css
│ │ └── layout.js
│ ├── .gitignore
│ ├── package.json
│ ├── styles
│ │ ├── global.css
│ │ └── utils.module.css
│ ├── posts
│ │ ├── pre-rendering.md
│ │ └── ssg-ssr.md
│ └── lib
│ │ └── posts.js
├── learn-starter
│ ├── .nvmrc
│ ├── README.md
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── package.json
│ ├── .gitignore
│ └── styles
│ │ ├── global.css
│ │ └── Home.module.css
├── typescript-final
│ ├── .nvmrc
│ ├── README.md
│ ├── global.d.ts
│ ├── public
│ │ ├── favicon.ico
│ │ └── images
│ │ │ └── profile.jpg
│ ├── pages
│ │ ├── api
│ │ │ └── hello.ts
│ │ ├── _app.tsx
│ │ ├── posts
│ │ │ └── [id].tsx
│ │ └── index.tsx
│ ├── next-env.d.ts
│ ├── components
│ │ ├── date.tsx
│ │ ├── layout.module.css
│ │ └── layout.tsx
│ ├── .gitignore
│ ├── package.json
│ ├── tsconfig.json
│ ├── styles
│ │ ├── global.css
│ │ └── utils.module.css
│ ├── posts
│ │ ├── pre-rendering.md
│ │ └── ssg-ssr.md
│ └── lib
│ │ └── posts.ts
├── api-routes-starter
│ ├── .nvmrc
│ ├── README.md
│ ├── public
│ │ ├── favicon.ico
│ │ └── images
│ │ │ └── profile.jpg
│ ├── pages
│ │ ├── _app.js
│ │ ├── posts
│ │ │ └── [id].js
│ │ └── index.js
│ ├── components
│ │ ├── date.js
│ │ ├── layout.module.css
│ │ └── layout.js
│ ├── .gitignore
│ ├── package.json
│ ├── styles
│ │ ├── global.css
│ │ └── utils.module.css
│ ├── posts
│ │ ├── pre-rendering.md
│ │ └── ssg-ssr.md
│ └── lib
│ │ └── posts.js
├── data-fetching-starter
│ ├── .nvmrc
│ ├── README.md
│ ├── public
│ │ ├── favicon.ico
│ │ └── images
│ │ │ └── profile.jpg
│ ├── pages
│ │ ├── _app.js
│ │ ├── posts
│ │ │ └── first-post.js
│ │ └── index.js
│ ├── components
│ │ ├── layout.module.css
│ │ └── layout.js
│ ├── package.json
│ ├── .gitignore
│ └── styles
│ │ ├── global.css
│ │ └── utils.module.css
├── dynamic-routes-starter
│ ├── .nvmrc
│ ├── README.md
│ ├── public
│ │ ├── favicon.ico
│ │ └── images
│ │ │ └── profile.jpg
│ ├── pages
│ │ ├── _app.js
│ │ ├── posts
│ │ │ └── first-post.js
│ │ └── index.js
│ ├── components
│ │ ├── layout.module.css
│ │ └── layout.js
│ ├── package.json
│ ├── .gitignore
│ ├── styles
│ │ ├── global.css
│ │ └── utils.module.css
│ ├── posts
│ │ ├── pre-rendering.md
│ │ └── ssg-ssr.md
│ └── lib
│ │ └── posts.js
├── dynamic-routes-step-1
│ ├── .nvmrc
│ ├── README.md
│ ├── public
│ │ ├── favicon.ico
│ │ └── images
│ │ │ └── profile.jpg
│ ├── pages
│ │ ├── _app.js
│ │ ├── posts
│ │ │ └── [id].js
│ │ └── index.js
│ ├── components
│ │ ├── layout.module.css
│ │ └── layout.js
│ ├── package.json
│ ├── .gitignore
│ ├── styles
│ │ ├── global.css
│ │ └── utils.module.css
│ ├── posts
│ │ ├── pre-rendering.md
│ │ └── ssg-ssr.md
│ └── lib
│ │ └── posts.js
├── assets-metadata-css-starter
│ ├── .nvmrc
│ ├── README.md
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── pages
│ │ └── posts
│ │ │ └── first-post.js
│ ├── package.json
│ ├── .gitignore
│ └── styles
│ │ ├── global.css
│ │ └── Home.module.css
├── navigate-between-pages-starter
│ ├── .nvmrc
│ ├── README.md
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── package.json
│ ├── .gitignore
│ └── styles
│ │ ├── global.css
│ │ └── Home.module.css
├── README.md
├── snippets
│ └── link-classname-example.js
└── errors
│ └── install.md
├── .gitignore
├── .prettierignore
├── seo
├── public
│ ├── favicon.ico
│ ├── large-image.jpg
│ └── vercel.svg
├── demo
│ ├── public
│ │ ├── favicon.ico
│ │ ├── large-image.jpg
│ │ └── vercel.svg
│ ├── README.md
│ ├── pages
│ │ └── _app.js
│ ├── package.json
│ ├── .gitignore
│ ├── styles
│ │ ├── global.css
│ │ └── Home.module.css
│ ├── components
│ │ └── CodeSampleModal.js
│ └── countries.js
├── pages
│ └── _app.js
├── README.md
├── package.json
├── .gitignore
├── styles
│ ├── global.css
│ └── Home.module.css
├── components
│ └── CodeSampleModal.js
└── countries.js
├── dashboard
├── final-example
│ ├── app
│ │ ├── favicon.ico
│ │ ├── opengraph-image.png
│ │ ├── dashboard
│ │ │ ├── (overview)
│ │ │ │ ├── loading.tsx
│ │ │ │ └── page.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── customers
│ │ │ │ └── page.tsx
│ │ │ └── invoices
│ │ │ │ ├── [id]
│ │ │ │ └── edit
│ │ │ │ │ ├── not-found.tsx
│ │ │ │ │ └── page.tsx
│ │ │ │ ├── create
│ │ │ │ └── page.tsx
│ │ │ │ ├── error.tsx
│ │ │ │ └── page.tsx
│ │ ├── ui
│ │ │ ├── fonts.ts
│ │ │ ├── global.css
│ │ │ ├── acme-logo.tsx
│ │ │ ├── button.tsx
│ │ │ ├── invoices
│ │ │ │ ├── status.tsx
│ │ │ │ ├── breadcrumbs.tsx
│ │ │ │ └── buttons.tsx
│ │ │ ├── dashboard
│ │ │ │ ├── sidenav.tsx
│ │ │ │ ├── nav-links.tsx
│ │ │ │ ├── cards.tsx
│ │ │ │ ├── revenue-chart.tsx
│ │ │ │ └── latest-invoices.tsx
│ │ │ ├── search.tsx
│ │ │ └── login-form.tsx
│ │ ├── layout.tsx
│ │ ├── login
│ │ │ └── page.tsx
│ │ ├── query
│ │ │ └── route.ts
│ │ ├── page.tsx
│ │ └── lib
│ │ │ ├── utils.ts
│ │ │ └── definitions.ts
│ ├── postcss.config.js
│ ├── public
│ │ ├── hero-desktop.png
│ │ ├── hero-mobile.png
│ │ └── customers
│ │ │ ├── amy-burns.png
│ │ │ ├── balazs-orban.png
│ │ │ ├── evil-rabbit.png
│ │ │ ├── lee-robinson.png
│ │ │ ├── delba-de-oliveira.png
│ │ │ └── michael-novotny.png
│ ├── next.config.ts
│ ├── README.md
│ ├── middleware.ts
│ ├── .env.example
│ ├── .gitignore
│ ├── tailwind.config.ts
│ ├── package.json
│ ├── tsconfig.json
│ ├── auth.config.ts
│ └── auth.ts
├── starter-example
│ ├── public
│ │ ├── favicon.ico
│ │ ├── hero-desktop.png
│ │ ├── hero-mobile.png
│ │ ├── opengraph-image.png
│ │ └── customers
│ │ │ ├── amy-burns.png
│ │ │ ├── balazs-orban.png
│ │ │ ├── evil-rabbit.png
│ │ │ ├── lee-robinson.png
│ │ │ ├── michael-novotny.png
│ │ │ └── delba-de-oliveira.png
│ ├── postcss.config.js
│ ├── next.config.ts
│ ├── app
│ │ ├── layout.tsx
│ │ ├── ui
│ │ │ ├── global.css
│ │ │ ├── acme-logo.tsx
│ │ │ ├── search.tsx
│ │ │ ├── button.tsx
│ │ │ ├── invoices
│ │ │ │ ├── status.tsx
│ │ │ │ ├── breadcrumbs.tsx
│ │ │ │ └── buttons.tsx
│ │ │ ├── dashboard
│ │ │ │ ├── nav-links.tsx
│ │ │ │ ├── sidenav.tsx
│ │ │ │ ├── cards.tsx
│ │ │ │ ├── revenue-chart.tsx
│ │ │ │ └── latest-invoices.tsx
│ │ │ └── login-form.tsx
│ │ ├── query
│ │ │ └── route.ts
│ │ ├── page.tsx
│ │ └── lib
│ │ │ ├── utils.ts
│ │ │ └── definitions.ts
│ ├── README.md
│ ├── .env.example
│ ├── .gitignore
│ ├── tailwind.config.ts
│ ├── package.json
│ └── tsconfig.json
└── README.md
├── .github
├── dependabot.yml
└── workflows
│ └── test.yml
├── prettier.config.js
├── .eslintrc.js
├── package.json
├── README.md
└── license.md
/basics/demo/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
2 |
--------------------------------------------------------------------------------
/basics/basics-final/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
2 |
--------------------------------------------------------------------------------
/basics/learn-starter/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
2 |
--------------------------------------------------------------------------------
/basics/typescript-final/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
2 |
--------------------------------------------------------------------------------
/basics/api-routes-starter/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
2 |
--------------------------------------------------------------------------------
/basics/data-fetching-starter/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
2 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
2 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
2 |
--------------------------------------------------------------------------------
/basics/assets-metadata-css-starter/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 |
--------------------------------------------------------------------------------
/basics/navigate-between-pages-starter/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | **/.next
2 | **/node_modules
3 | **/package-lock.json
4 | **/pnpm-lock.yaml
5 |
--------------------------------------------------------------------------------
/basics/demo/README.md:
--------------------------------------------------------------------------------
1 | This is a final template for [Learn Next.js](https://nextjs.org/learn).
2 |
--------------------------------------------------------------------------------
/seo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/seo/public/favicon.ico
--------------------------------------------------------------------------------
/basics/basics-final/README.md:
--------------------------------------------------------------------------------
1 | This is a starter template for [Learn Next.js](https://nextjs.org/learn).
2 |
--------------------------------------------------------------------------------
/basics/learn-starter/README.md:
--------------------------------------------------------------------------------
1 | This is a starter template for [Learn Next.js](https://nextjs.org/learn).
2 |
--------------------------------------------------------------------------------
/seo/demo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/seo/demo/public/favicon.ico
--------------------------------------------------------------------------------
/seo/public/large-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/seo/public/large-image.jpg
--------------------------------------------------------------------------------
/basics/api-routes-starter/README.md:
--------------------------------------------------------------------------------
1 | This is a starter template for [Learn Next.js](https://nextjs.org/learn).
2 |
--------------------------------------------------------------------------------
/basics/typescript-final/README.md:
--------------------------------------------------------------------------------
1 | This is a starter template for [Learn Next.js](https://nextjs.org/learn).
2 |
--------------------------------------------------------------------------------
/basics/data-fetching-starter/README.md:
--------------------------------------------------------------------------------
1 | This is a starter template for [Learn Next.js](https://nextjs.org/learn).
2 |
--------------------------------------------------------------------------------
/basics/demo/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/demo/public/favicon.ico
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/README.md:
--------------------------------------------------------------------------------
1 | This is a starter template for [Learn Next.js](https://nextjs.org/learn).
2 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/README.md:
--------------------------------------------------------------------------------
1 | This is a starter template for [Learn Next.js](https://nextjs.org/learn).
2 |
--------------------------------------------------------------------------------
/seo/demo/public/large-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/seo/demo/public/large-image.jpg
--------------------------------------------------------------------------------
/basics/assets-metadata-css-starter/README.md:
--------------------------------------------------------------------------------
1 | This is a starter template for [Learn Next.js](https://nextjs.org/learn).
2 |
--------------------------------------------------------------------------------
/basics/demo/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | export default (req, res) => {
2 | res.status(200).json({ text: 'Hello' });
3 | };
4 |
--------------------------------------------------------------------------------
/basics/basics-final/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | export default (req, res) => {
2 | res.status(200).json({ text: 'Hello' });
3 | };
4 |
--------------------------------------------------------------------------------
/basics/demo/public/images/profile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/demo/public/images/profile.jpg
--------------------------------------------------------------------------------
/basics/navigate-between-pages-starter/README.md:
--------------------------------------------------------------------------------
1 | This is a starter template for [Learn Next.js](https://nextjs.org/learn).
2 |
--------------------------------------------------------------------------------
/basics/typescript-final/global.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'remark-html' {
2 | const html: any;
3 | export default html;
4 | }
5 |
--------------------------------------------------------------------------------
/seo/demo/README.md:
--------------------------------------------------------------------------------
1 | This is the final demo for the [Learn SEO](https://nextjs.org/learn/seo/introduction-to-seo) example.
2 |
--------------------------------------------------------------------------------
/basics/basics-final/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/basics-final/public/favicon.ico
--------------------------------------------------------------------------------
/basics/learn-starter/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/learn-starter/public/favicon.ico
--------------------------------------------------------------------------------
/dashboard/final-example/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/final-example/app/favicon.ico
--------------------------------------------------------------------------------
/basics/typescript-final/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/typescript-final/public/favicon.ico
--------------------------------------------------------------------------------
/basics/api-routes-starter/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/api-routes-starter/public/favicon.ico
--------------------------------------------------------------------------------
/dashboard/starter-example/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/starter-example/public/favicon.ico
--------------------------------------------------------------------------------
/basics/basics-final/public/images/profile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/basics-final/public/images/profile.jpg
--------------------------------------------------------------------------------
/basics/data-fetching-starter/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/data-fetching-starter/public/favicon.ico
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/dynamic-routes-step-1/public/favicon.ico
--------------------------------------------------------------------------------
/dashboard/final-example/app/opengraph-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/final-example/app/opengraph-image.png
--------------------------------------------------------------------------------
/dashboard/final-example/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/dashboard/final-example/public/hero-desktop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/final-example/public/hero-desktop.png
--------------------------------------------------------------------------------
/dashboard/final-example/public/hero-mobile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/final-example/public/hero-mobile.png
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/dynamic-routes-starter/public/favicon.ico
--------------------------------------------------------------------------------
/basics/typescript-final/public/images/profile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/typescript-final/public/images/profile.jpg
--------------------------------------------------------------------------------
/dashboard/starter-example/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/dashboard/starter-example/public/hero-desktop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/starter-example/public/hero-desktop.png
--------------------------------------------------------------------------------
/dashboard/starter-example/public/hero-mobile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/starter-example/public/hero-mobile.png
--------------------------------------------------------------------------------
/basics/api-routes-starter/public/images/profile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/api-routes-starter/public/images/profile.jpg
--------------------------------------------------------------------------------
/dashboard/starter-example/public/opengraph-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/starter-example/public/opengraph-image.png
--------------------------------------------------------------------------------
/basics/assets-metadata-css-starter/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/assets-metadata-css-starter/public/favicon.ico
--------------------------------------------------------------------------------
/basics/data-fetching-starter/public/images/profile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/data-fetching-starter/public/images/profile.jpg
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/public/images/profile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/dynamic-routes-step-1/public/images/profile.jpg
--------------------------------------------------------------------------------
/dashboard/final-example/public/customers/amy-burns.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/final-example/public/customers/amy-burns.png
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: 'github-actions'
4 | directory: '/'
5 | schedule:
6 | interval: 'weekly'
7 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/public/images/profile.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/dynamic-routes-starter/public/images/profile.jpg
--------------------------------------------------------------------------------
/basics/navigate-between-pages-starter/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/basics/navigate-between-pages-starter/public/favicon.ico
--------------------------------------------------------------------------------
/dashboard/final-example/public/customers/balazs-orban.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/final-example/public/customers/balazs-orban.png
--------------------------------------------------------------------------------
/dashboard/final-example/public/customers/evil-rabbit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/final-example/public/customers/evil-rabbit.png
--------------------------------------------------------------------------------
/dashboard/final-example/public/customers/lee-robinson.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/final-example/public/customers/lee-robinson.png
--------------------------------------------------------------------------------
/dashboard/starter-example/public/customers/amy-burns.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/starter-example/public/customers/amy-burns.png
--------------------------------------------------------------------------------
/seo/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/global.css';
2 |
3 | export default function MyApp({ Component, pageProps }) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/dashboard/starter-example/public/customers/balazs-orban.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/starter-example/public/customers/balazs-orban.png
--------------------------------------------------------------------------------
/dashboard/starter-example/public/customers/evil-rabbit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/starter-example/public/customers/evil-rabbit.png
--------------------------------------------------------------------------------
/dashboard/starter-example/public/customers/lee-robinson.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/starter-example/public/customers/lee-robinson.png
--------------------------------------------------------------------------------
/seo/demo/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/global.css';
2 |
3 | export default function MyApp({ Component, pageProps }) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/basics/demo/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/global.css';
2 |
3 | export default function App({ Component, pageProps }) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/dashboard/final-example/public/customers/delba-de-oliveira.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/final-example/public/customers/delba-de-oliveira.png
--------------------------------------------------------------------------------
/dashboard/final-example/public/customers/michael-novotny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/final-example/public/customers/michael-novotny.png
--------------------------------------------------------------------------------
/dashboard/starter-example/public/customers/michael-novotny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/starter-example/public/customers/michael-novotny.png
--------------------------------------------------------------------------------
/basics/basics-final/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/global.css';
2 |
3 | export default function App({ Component, pageProps }) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/dashboard/starter-example/public/customers/delba-de-oliveira.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wolf620/next-learn/HEAD/dashboard/starter-example/public/customers/delba-de-oliveira.png
--------------------------------------------------------------------------------
/basics/api-routes-starter/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/global.css';
2 |
3 | export default function App({ Component, pageProps }) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/basics/data-fetching-starter/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/global.css';
2 |
3 | export default function App({ Component, pageProps }) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/global.css';
2 |
3 | export default function App({ Component, pageProps }) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/pages/_app.js:
--------------------------------------------------------------------------------
1 | import '../styles/global.css';
2 |
3 | export default function App({ Component, pageProps }) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/dashboard/(overview)/loading.tsx:
--------------------------------------------------------------------------------
1 | import DashboardSkeleton from '@/app/ui/skeletons';
2 |
3 | export default function Loading() {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/dashboard/final-example/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from 'next';
2 |
3 | const nextConfig: NextConfig = {
4 | /* config options here */
5 | };
6 |
7 | export default nextConfig;
8 |
--------------------------------------------------------------------------------
/dashboard/starter-example/next.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextConfig } from 'next';
2 |
3 | const nextConfig: NextConfig = {
4 | /* config options here */
5 | };
6 |
7 | export default nextConfig;
8 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | const styleguide = require('@vercel/style-guide/prettier');
2 |
3 | module.exports = {
4 | ...styleguide,
5 | plugins: [...styleguide.plugins, 'prettier-plugin-tailwindcss'],
6 | };
7 |
--------------------------------------------------------------------------------
/basics/typescript-final/pages/api/hello.ts:
--------------------------------------------------------------------------------
1 | import { NextApiRequest, NextApiResponse } from 'next';
2 |
3 | export default (_: NextApiRequest, res: NextApiResponse) => {
4 | res.status(200).json({ text: 'Hello' });
5 | };
6 |
--------------------------------------------------------------------------------
/basics/typescript-final/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import '../styles/global.css';
2 | import { AppProps } from 'next/app';
3 |
4 | export default function App({ Component, pageProps }: AppProps) {
5 | return ;
6 | }
7 |
--------------------------------------------------------------------------------
/basics/typescript-final/next-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 | ///
3 |
4 | // NOTE: This file should not be edited
5 | // see https://nextjs.org/docs/basic-features/typescript for more information.
6 |
--------------------------------------------------------------------------------
/basics/demo/components/date.js:
--------------------------------------------------------------------------------
1 | import { parseISO, format } from 'date-fns';
2 |
3 | export default function Date({ dateString }) {
4 | const date = parseISO(dateString);
5 | return {format(date, 'LLLL d, yyyy')} ;
6 | }
7 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/ui/fonts.ts:
--------------------------------------------------------------------------------
1 | import { Inter, Lusitana } from 'next/font/google';
2 |
3 | export const inter = Inter({ subsets: ['latin'] });
4 |
5 | export const lusitana = Lusitana({
6 | weight: ['400', '700'],
7 | subsets: ['latin'],
8 | });
9 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/layout.tsx:
--------------------------------------------------------------------------------
1 | export default function RootLayout({
2 | children,
3 | }: {
4 | children: React.ReactNode;
5 | }) {
6 | return (
7 |
8 |
{children}
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/seo/README.md:
--------------------------------------------------------------------------------
1 | # next-seo-starter
2 |
3 | This repository contains the starter template for the [Improving Web Vitals](https://nextjs.org/learn/seo/improve/lighthouse) example of the Next.js [learn SEO course](https://nextjs.org/learn/seo/introduction-to-seo).
4 |
--------------------------------------------------------------------------------
/basics/basics-final/components/date.js:
--------------------------------------------------------------------------------
1 | import { parseISO, format } from 'date-fns';
2 |
3 | export default function Date({ dateString }) {
4 | const date = parseISO(dateString);
5 | return {format(date, 'LLLL d, yyyy')} ;
6 | }
7 |
--------------------------------------------------------------------------------
/basics/api-routes-starter/components/date.js:
--------------------------------------------------------------------------------
1 | import { parseISO, format } from 'date-fns';
2 |
3 | export default function Date({ dateString }) {
4 | const date = parseISO(dateString);
5 | return {format(date, 'LLLL d, yyyy')} ;
6 | }
7 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['next/core-web-vitals', 'prettier'],
3 | ignorePatterns: ['**/.next/**', '**/node_modules/**'],
4 | root: true,
5 | settings: {
6 | next: {
7 | rootDir: ['basics/*/', 'dashboard/*/', 'seo/'],
8 | },
9 | },
10 | };
11 |
--------------------------------------------------------------------------------
/basics/typescript-final/components/date.tsx:
--------------------------------------------------------------------------------
1 | import { parseISO, format } from 'date-fns';
2 |
3 | export default function Date({ dateString }: { dateString: string }) {
4 | const date = parseISO(dateString);
5 | return {format(date, 'LLLL d, yyyy')} ;
6 | }
7 |
--------------------------------------------------------------------------------
/basics/demo/components/layout.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | max-width: 36rem;
3 | padding: 0 1rem;
4 | margin: 3rem auto 6rem;
5 | }
6 |
7 | .header {
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | }
12 |
13 | .backToHome {
14 | margin: 3rem 0 0;
15 | }
16 |
--------------------------------------------------------------------------------
/dashboard/final-example/README.md:
--------------------------------------------------------------------------------
1 | ## Next.js App Router Course - Final
2 |
3 | This is the final template for the Next.js App Router Course. It contains the final code for the dashboard application.
4 |
5 | For more information, see the [course curriculum](https://nextjs.org/learn) on the Next.js Website.
6 |
--------------------------------------------------------------------------------
/basics/assets-metadata-css-starter/pages/posts/first-post.js:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 |
3 | export default function FirstPost() {
4 | return (
5 | <>
6 | First Post
7 |
8 | Back to home
9 |
10 | >
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/basics/basics-final/components/layout.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | max-width: 36rem;
3 | padding: 0 1rem;
4 | margin: 3rem auto 6rem;
5 | }
6 |
7 | .header {
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | }
12 |
13 | .backToHome {
14 | margin: 3rem 0 0;
15 | }
16 |
--------------------------------------------------------------------------------
/dashboard/README.md:
--------------------------------------------------------------------------------
1 | # Next.js App Router Course - Build a Dashboard
2 |
3 | This repository contains the starter templates for the [Next.js App Router Course](https://nextjs.org/learn), separated by chapters.
4 |
5 | For more information, see the [course curriculum](https://nextjs.org/learn) on the Next.js Website.
6 |
--------------------------------------------------------------------------------
/dashboard/starter-example/README.md:
--------------------------------------------------------------------------------
1 | ## Next.js App Router Course - Starter
2 |
3 | This is the starter template for the Next.js App Router Course. It contains the starting code for the dashboard application.
4 |
5 | For more information, see the [course curriculum](https://nextjs.org/learn) on the Next.js Website.
6 |
--------------------------------------------------------------------------------
/basics/api-routes-starter/components/layout.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | max-width: 36rem;
3 | padding: 0 1rem;
4 | margin: 3rem auto 6rem;
5 | }
6 |
7 | .header {
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | }
12 |
13 | .backToHome {
14 | margin: 3rem 0 0;
15 | }
16 |
--------------------------------------------------------------------------------
/basics/typescript-final/components/layout.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | max-width: 36rem;
3 | padding: 0 1rem;
4 | margin: 3rem auto 6rem;
5 | }
6 |
7 | .header {
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | }
12 |
13 | .backToHome {
14 | margin: 3rem 0 0;
15 | }
16 |
--------------------------------------------------------------------------------
/basics/data-fetching-starter/components/layout.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | max-width: 36rem;
3 | padding: 0 1rem;
4 | margin: 3rem auto 6rem;
5 | }
6 |
7 | .header {
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | }
12 |
13 | .backToHome {
14 | margin: 3rem 0 0;
15 | }
16 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/components/layout.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | max-width: 36rem;
3 | padding: 0 1rem;
4 | margin: 3rem auto 6rem;
5 | }
6 |
7 | .header {
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | }
12 |
13 | .backToHome {
14 | margin: 3rem 0 0;
15 | }
16 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/components/layout.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | max-width: 36rem;
3 | padding: 0 1rem;
4 | margin: 3rem auto 6rem;
5 | }
6 |
7 | .header {
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | }
12 |
13 | .backToHome {
14 | margin: 3rem 0 0;
15 | }
16 |
--------------------------------------------------------------------------------
/basics/README.md:
--------------------------------------------------------------------------------
1 | # next-learn-starter
2 |
3 | This repository contains starter templates for [Learn Next.js](https://nextjs.org/learn).
4 |
5 | The final result for the basics lesson can be found in the [demo](demo) directory and is available at: [https://next-learn-starter.vercel.app/](https://next-learn-starter.vercel.app/).
6 |
--------------------------------------------------------------------------------
/basics/learn-starter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "next build",
5 | "dev": "next dev",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "next": "latest",
10 | "react": "latest",
11 | "react-dom": "latest"
12 | },
13 | "engines": {
14 | "node": ">=18"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/basics/data-fetching-starter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "next build",
5 | "dev": "next dev",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "next": "latest",
10 | "react": "latest",
11 | "react-dom": "latest"
12 | },
13 | "engines": {
14 | "node": ">=18"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/dashboard/final-example/middleware.ts:
--------------------------------------------------------------------------------
1 | import NextAuth from 'next-auth';
2 | import { authConfig } from './auth.config';
3 |
4 | export default NextAuth(authConfig).auth;
5 |
6 | export const config = {
7 | // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
8 | matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
9 | };
10 |
--------------------------------------------------------------------------------
/basics/navigate-between-pages-starter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "next build",
5 | "dev": "next dev",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "next": "latest",
10 | "react": "latest",
11 | "react-dom": "latest"
12 | },
13 | "engines": {
14 | "node": ">=18"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/basics/assets-metadata-css-starter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "next build",
5 | "dev": "next dev --turbopack",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "next": "latest",
10 | "react": "latest",
11 | "react-dom": "latest"
12 | },
13 | "engines": {
14 | "node": ">=18"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "next build",
5 | "dev": "next dev",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "gray-matter": "^4.0.3",
10 | "next": "latest",
11 | "react": "latest",
12 | "react-dom": "latest"
13 | },
14 | "engines": {
15 | "node": ">=18"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "next build",
5 | "dev": "next dev",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "gray-matter": "^4.0.3",
10 | "next": "latest",
11 | "react": "latest",
12 | "react-dom": "latest"
13 | },
14 | "engines": {
15 | "node": ">=18"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/basics/demo/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | .env*
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/basics/basics-final/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | .env*
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/basics/typescript-final/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | .env*
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/basics/api-routes-starter/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | .env*
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/basics/data-fetching-starter/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | .env*
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | .env*
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | .env*
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/basics/snippets/link-classname-example.js:
--------------------------------------------------------------------------------
1 | // Example: Adding className with
2 | import Link from 'next/link';
3 |
4 | export default function LinkClassnameExample() {
5 | return (
6 |
7 | Hello World
8 |
9 | );
10 | }
11 |
12 | // Take a look at https://nextjs.org/docs/api-reference/next/link
13 | // to learn more!
14 |
--------------------------------------------------------------------------------
/basics/assets-metadata-css-starter/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | .env*
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/dashboard/final-example/.env.example:
--------------------------------------------------------------------------------
1 | # Copy from .env.local on the Vercel dashboard
2 | # https://nextjs.org/learn/dashboard-app/setting-up-your-database#create-a-postgres-database
3 | POSTGRES_URL=
4 | POSTGRES_PRISMA_URL=
5 | POSTGRES_URL_NON_POOLING=
6 | POSTGRES_USER=
7 | POSTGRES_HOST=
8 | POSTGRES_PASSWORD=
9 | POSTGRES_DATABASE=
10 |
11 | # `openssl rand -base64 32`
12 | AUTH_SECRET=
13 | AUTH_URL=http://localhost:3000/api/auth
--------------------------------------------------------------------------------
/basics/navigate-between-pages-starter/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | .env*
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
--------------------------------------------------------------------------------
/dashboard/starter-example/.env.example:
--------------------------------------------------------------------------------
1 | # Copy from .env.local on the Vercel dashboard
2 | # https://nextjs.org/learn/dashboard-app/setting-up-your-database#create-a-postgres-database
3 | POSTGRES_URL=
4 | POSTGRES_PRISMA_URL=
5 | POSTGRES_URL_NON_POOLING=
6 | POSTGRES_USER=
7 | POSTGRES_HOST=
8 | POSTGRES_PASSWORD=
9 | POSTGRES_DATABASE=
10 |
11 | # `openssl rand -base64 32`
12 | AUTH_SECRET=
13 | AUTH_URL=http://localhost:3000/api/auth
--------------------------------------------------------------------------------
/dashboard/final-example/app/ui/global.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | input[type='number'] {
6 | -moz-appearance: textfield;
7 | appearance: textfield;
8 | }
9 |
10 | input[type='number']::-webkit-inner-spin-button {
11 | -webkit-appearance: none;
12 | margin: 0;
13 | }
14 |
15 | input[type='number']::-webkit-outer-spin-button {
16 | -webkit-appearance: none;
17 | margin: 0;
18 | }
19 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/ui/global.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | input[type='number'] {
6 | -moz-appearance: textfield;
7 | appearance: textfield;
8 | }
9 |
10 | input[type='number']::-webkit-inner-spin-button {
11 | -webkit-appearance: none;
12 | margin: 0;
13 | }
14 |
15 | input[type='number']::-webkit-outer-spin-button {
16 | -webkit-appearance: none;
17 | margin: 0;
18 | }
19 |
--------------------------------------------------------------------------------
/basics/data-fetching-starter/pages/posts/first-post.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import Layout from '../../components/layout';
4 |
5 | export default function FirstPost() {
6 | return (
7 |
8 |
9 | First Post
10 |
11 | First Post
12 |
13 | Back to home
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/pages/posts/first-post.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Link from 'next/link';
3 | import Layout from '../../components/layout';
4 |
5 | export default function FirstPost() {
6 | return (
7 |
8 |
9 | First Post
10 |
11 | First Post
12 |
13 | Back to home
14 |
15 |
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/basics/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "next build",
5 | "dev": "next dev",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "date-fns": "^2.29.3",
10 | "gray-matter": "^4.0.3",
11 | "next": "latest",
12 | "react": "latest",
13 | "react-dom": "latest",
14 | "remark": "^14.0.2",
15 | "remark-html": "^15.0.1"
16 | },
17 | "engines": {
18 | "node": ">=18"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/ui/acme-logo.tsx:
--------------------------------------------------------------------------------
1 | import { GlobeAltIcon } from '@heroicons/react/24/outline';
2 | import { lusitana } from '@/app/ui/fonts';
3 |
4 | export default function AcmeLogo() {
5 | return (
6 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/ui/acme-logo.tsx:
--------------------------------------------------------------------------------
1 | import { GlobeAltIcon } from '@heroicons/react/24/outline';
2 | import { lusitana } from '@/app/ui/fonts';
3 |
4 | export default function AcmeLogo() {
5 | return (
6 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/basics/basics-final/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "next build",
5 | "dev": "next dev",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "date-fns": "^2.29.3",
10 | "gray-matter": "^4.0.3",
11 | "next": "latest",
12 | "react": "latest",
13 | "react-dom": "latest",
14 | "remark": "^14.0.2",
15 | "remark-html": "^15.0.1"
16 | },
17 | "engines": {
18 | "node": ">=18"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/dashboard/layout.tsx:
--------------------------------------------------------------------------------
1 | import SideNav from '@/app/ui/dashboard/sidenav';
2 |
3 | export default function Layout({ children }: { children: React.ReactNode }) {
4 | return (
5 |
6 |
7 |
8 |
9 |
{children}
10 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/seo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "next build",
5 | "dev": "next dev --turbopack",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "fuse.js": "^6.6.2",
10 | "lodash": "^4.17.21",
11 | "next": "^15.1.6",
12 | "react": "latest",
13 | "react-dom": "latest",
14 | "react-modal": "^3.16.3",
15 | "react-syntax-highlighter": "^15.6.1"
16 | },
17 | "engines": {
18 | "node": ">=18"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/seo/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "next build",
5 | "dev": "next dev --turbopack",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "fuse.js": "^6.6.2",
10 | "lodash": "^4.17.21",
11 | "next": "^15.1.6",
12 | "react": "latest",
13 | "react-dom": "latest",
14 | "react-modal": "^3.16.3",
15 | "react-syntax-highlighter": "^15.6.1"
16 | },
17 | "engines": {
18 | "node": ">=18"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/basics/api-routes-starter/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "next build",
5 | "dev": "next dev --turbopack",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "date-fns": "^2.29.3",
10 | "gray-matter": "^4.0.3",
11 | "next": "latest",
12 | "react": "latest",
13 | "react-dom": "latest",
14 | "remark": "^14.0.2",
15 | "remark-html": "^15.0.1"
16 | },
17 | "engines": {
18 | "node": ">=18"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/basics/learn-starter/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 |
21 | # debug
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | # local env files
27 | .env.local
28 | .env.development.local
29 | .env.test.local
30 | .env.production.local
31 |
--------------------------------------------------------------------------------
/basics/learn-starter/styles/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family:
6 | Inter,
7 | -apple-system,
8 | BlinkMacSystemFont,
9 | Segoe UI,
10 | Roboto,
11 | Oxygen,
12 | Ubuntu,
13 | Cantarell,
14 | Fira Sans,
15 | Droid Sans,
16 | Helvetica Neue,
17 | sans-serif;
18 | }
19 |
20 | a {
21 | color: inherit;
22 | text-decoration: none;
23 | }
24 |
25 | * {
26 | box-sizing: border-box;
27 | }
28 |
29 | img {
30 | max-width: 100%;
31 | height: auto;
32 | }
33 |
--------------------------------------------------------------------------------
/seo/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/seo/demo/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env.local
29 | .env.development.local
30 | .env.test.local
31 | .env.production.local
32 |
33 | # vercel
34 | .vercel
35 |
--------------------------------------------------------------------------------
/basics/assets-metadata-css-starter/styles/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family:
6 | Inter,
7 | -apple-system,
8 | BlinkMacSystemFont,
9 | Segoe UI,
10 | Roboto,
11 | Oxygen,
12 | Ubuntu,
13 | Cantarell,
14 | Fira Sans,
15 | Droid Sans,
16 | Helvetica Neue,
17 | sans-serif;
18 | }
19 |
20 | a {
21 | color: inherit;
22 | text-decoration: none;
23 | }
24 |
25 | * {
26 | box-sizing: border-box;
27 | }
28 |
29 | img {
30 | max-width: 100%;
31 | height: auto;
32 | }
33 |
--------------------------------------------------------------------------------
/basics/navigate-between-pages-starter/styles/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family:
6 | Inter,
7 | -apple-system,
8 | BlinkMacSystemFont,
9 | Segoe UI,
10 | Roboto,
11 | Oxygen,
12 | Ubuntu,
13 | Cantarell,
14 | Fira Sans,
15 | Droid Sans,
16 | Helvetica Neue,
17 | sans-serif;
18 | }
19 |
20 | a {
21 | color: inherit;
22 | text-decoration: none;
23 | }
24 |
25 | * {
26 | box-sizing: border-box;
27 | }
28 |
29 | img {
30 | max-width: 100%;
31 | height: auto;
32 | }
33 |
--------------------------------------------------------------------------------
/dashboard/final-example/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 | .env
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/dashboard/starter-example/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 | /build
17 |
18 | # misc
19 | .DS_Store
20 | *.pem
21 |
22 | # debug
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # local env files
28 | .env*.local
29 | .env
30 |
31 | # vercel
32 | .vercel
33 |
34 | # typescript
35 | *.tsbuildinfo
36 | next-env.d.ts
37 |
--------------------------------------------------------------------------------
/basics/typescript-final/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "next build",
5 | "dev": "next dev",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "date-fns": "^2.29.3",
10 | "gray-matter": "^4.0.3",
11 | "next": "^13.0.2",
12 | "react": "latest",
13 | "react-dom": "latest",
14 | "remark": "^14.0.2",
15 | "remark-html": "^15.0.1"
16 | },
17 | "devDependencies": {
18 | "@types/node": "^18.11.9",
19 | "@types/react": "^18.0.25",
20 | "typescript": "^4.8.4"
21 | },
22 | "engines": {
23 | "node": ">=18"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/basics/typescript-final/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "forceConsistentCasingInFileNames": true,
9 | "noEmit": true,
10 | "esModuleInterop": true,
11 | "module": "esnext",
12 | "moduleResolution": "node",
13 | "resolveJsonModule": true,
14 | "isolatedModules": true,
15 | "jsx": "preserve",
16 | "incremental": true
17 | },
18 | "exclude": ["node_modules"],
19 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]
20 | }
21 |
--------------------------------------------------------------------------------
/basics/demo/styles/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family:
6 | -apple-system,
7 | BlinkMacSystemFont,
8 | Segoe UI,
9 | Roboto,
10 | Oxygen,
11 | Ubuntu,
12 | Cantarell,
13 | Fira Sans,
14 | Droid Sans,
15 | Helvetica Neue,
16 | sans-serif;
17 | line-height: 1.6;
18 | font-size: 18px;
19 | }
20 |
21 | * {
22 | box-sizing: border-box;
23 | }
24 |
25 | a {
26 | color: #0070f3;
27 | text-decoration: none;
28 | }
29 |
30 | a:hover {
31 | text-decoration: underline;
32 | }
33 |
34 | img {
35 | max-width: 100%;
36 | display: block;
37 | }
38 |
--------------------------------------------------------------------------------
/basics/basics-final/styles/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family:
6 | -apple-system,
7 | BlinkMacSystemFont,
8 | Segoe UI,
9 | Roboto,
10 | Oxygen,
11 | Ubuntu,
12 | Cantarell,
13 | Fira Sans,
14 | Droid Sans,
15 | Helvetica Neue,
16 | sans-serif;
17 | line-height: 1.6;
18 | font-size: 18px;
19 | }
20 |
21 | * {
22 | box-sizing: border-box;
23 | }
24 |
25 | a {
26 | color: #0070f3;
27 | text-decoration: none;
28 | }
29 |
30 | a:hover {
31 | text-decoration: underline;
32 | }
33 |
34 | img {
35 | max-width: 100%;
36 | display: block;
37 | }
38 |
--------------------------------------------------------------------------------
/basics/api-routes-starter/styles/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family:
6 | -apple-system,
7 | BlinkMacSystemFont,
8 | Segoe UI,
9 | Roboto,
10 | Oxygen,
11 | Ubuntu,
12 | Cantarell,
13 | Fira Sans,
14 | Droid Sans,
15 | Helvetica Neue,
16 | sans-serif;
17 | line-height: 1.6;
18 | font-size: 18px;
19 | }
20 |
21 | * {
22 | box-sizing: border-box;
23 | }
24 |
25 | a {
26 | color: #0070f3;
27 | text-decoration: none;
28 | }
29 |
30 | a:hover {
31 | text-decoration: underline;
32 | }
33 |
34 | img {
35 | max-width: 100%;
36 | display: block;
37 | }
38 |
--------------------------------------------------------------------------------
/basics/typescript-final/styles/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family:
6 | -apple-system,
7 | BlinkMacSystemFont,
8 | Segoe UI,
9 | Roboto,
10 | Oxygen,
11 | Ubuntu,
12 | Cantarell,
13 | Fira Sans,
14 | Droid Sans,
15 | Helvetica Neue,
16 | sans-serif;
17 | line-height: 1.6;
18 | font-size: 18px;
19 | }
20 |
21 | * {
22 | box-sizing: border-box;
23 | }
24 |
25 | a {
26 | color: #0070f3;
27 | text-decoration: none;
28 | }
29 |
30 | a:hover {
31 | text-decoration: underline;
32 | }
33 |
34 | img {
35 | max-width: 100%;
36 | display: block;
37 | }
38 |
--------------------------------------------------------------------------------
/basics/data-fetching-starter/styles/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family:
6 | -apple-system,
7 | BlinkMacSystemFont,
8 | Segoe UI,
9 | Roboto,
10 | Oxygen,
11 | Ubuntu,
12 | Cantarell,
13 | Fira Sans,
14 | Droid Sans,
15 | Helvetica Neue,
16 | sans-serif;
17 | line-height: 1.6;
18 | font-size: 18px;
19 | }
20 |
21 | * {
22 | box-sizing: border-box;
23 | }
24 |
25 | a {
26 | color: #0070f3;
27 | text-decoration: none;
28 | }
29 |
30 | a:hover {
31 | text-decoration: underline;
32 | }
33 |
34 | img {
35 | max-width: 100%;
36 | display: block;
37 | }
38 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/styles/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family:
6 | -apple-system,
7 | BlinkMacSystemFont,
8 | Segoe UI,
9 | Roboto,
10 | Oxygen,
11 | Ubuntu,
12 | Cantarell,
13 | Fira Sans,
14 | Droid Sans,
15 | Helvetica Neue,
16 | sans-serif;
17 | line-height: 1.6;
18 | font-size: 18px;
19 | }
20 |
21 | * {
22 | box-sizing: border-box;
23 | }
24 |
25 | a {
26 | color: #0070f3;
27 | text-decoration: none;
28 | }
29 |
30 | a:hover {
31 | text-decoration: underline;
32 | }
33 |
34 | img {
35 | max-width: 100%;
36 | display: block;
37 | }
38 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/styles/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family:
6 | -apple-system,
7 | BlinkMacSystemFont,
8 | Segoe UI,
9 | Roboto,
10 | Oxygen,
11 | Ubuntu,
12 | Cantarell,
13 | Fira Sans,
14 | Droid Sans,
15 | Helvetica Neue,
16 | sans-serif;
17 | line-height: 1.6;
18 | font-size: 18px;
19 | }
20 |
21 | * {
22 | box-sizing: border-box;
23 | }
24 |
25 | a {
26 | color: #0070f3;
27 | text-decoration: none;
28 | }
29 |
30 | a:hover {
31 | text-decoration: underline;
32 | }
33 |
34 | img {
35 | max-width: 100%;
36 | display: block;
37 | }
38 |
--------------------------------------------------------------------------------
/basics/data-fetching-starter/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Layout, { siteTitle } from '../components/layout';
3 | import utilStyles from '../styles/utils.module.css';
4 |
5 | export default function Home() {
6 | return (
7 |
8 |
9 | {siteTitle}
10 |
11 |
12 | [Your Self Introduction]
13 |
14 | (This is a sample website - you’ll be building a site like this in{' '}
15 | our Next.js tutorial .)
16 |
17 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import '@/app/ui/global.css';
2 | import { inter } from '@/app/ui/fonts';
3 | import { Metadata } from 'next';
4 |
5 | export const metadata: Metadata = {
6 | title: {
7 | template: '%s | Acme Dashboard',
8 | default: 'Acme Dashboard',
9 | },
10 | description: 'The official Next.js Learn Dashboard built with App Router.',
11 | metadataBase: new URL('https://next-learn-dashboard.vercel.sh'),
12 | };
13 | export default function RootLayout({
14 | children,
15 | }: {
16 | children: React.ReactNode;
17 | }) {
18 | return (
19 |
20 | {children}
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/dashboard/customers/page.tsx:
--------------------------------------------------------------------------------
1 | import { fetchFilteredCustomers } from '@/app/lib/data';
2 | import CustomersTable from '@/app/ui/customers/table';
3 | import { Metadata } from 'next';
4 |
5 | export const metadata: Metadata = {
6 | title: 'Customers',
7 | };
8 |
9 | export default async function Page(props: {
10 | searchParams?: Promise<{
11 | query?: string;
12 | page?: string;
13 | }>;
14 | }) {
15 | const searchParams = await props.searchParams;
16 | const query = searchParams?.query || '';
17 |
18 | const customers = await fetchFilteredCustomers(query);
19 |
20 | return (
21 |
22 |
23 |
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/pages/posts/[id].js:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/layout';
2 | import { getAllPostIds, getPostData } from '../../lib/posts';
3 |
4 | export default function Post({ postData }) {
5 | return (
6 |
7 | {postData.title}
8 |
9 | {postData.id}
10 |
11 | {postData.date}
12 |
13 | );
14 | }
15 |
16 | export async function getStaticPaths() {
17 | const paths = getAllPostIds();
18 | return {
19 | paths,
20 | fallback: false,
21 | };
22 | }
23 |
24 | export async function getStaticProps({ params }) {
25 | const postData = getPostData(params.id);
26 | return {
27 | props: {
28 | postData,
29 | },
30 | };
31 | }
32 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/dashboard/invoices/[id]/edit/not-found.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import { FaceFrownIcon } from '@heroicons/react/24/outline';
3 |
4 | export default function NotFound() {
5 | return (
6 |
7 |
8 | 404 Not Found
9 | Could not find the requested invoice.
10 |
14 | Go Back
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/seo/styles/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family:
6 | Inter,
7 | -apple-system,
8 | BlinkMacSystemFont,
9 | Segoe UI,
10 | Roboto,
11 | Oxygen,
12 | Ubuntu,
13 | Cantarell,
14 | Fira Sans,
15 | Droid Sans,
16 | Helvetica Neue,
17 | sans-serif;
18 | }
19 |
20 | a {
21 | color: inherit;
22 | text-decoration: none;
23 | }
24 |
25 | * {
26 | box-sizing: border-box;
27 | }
28 |
29 | img {
30 | max-width: 100%;
31 | height: auto;
32 | }
33 |
34 | h1,
35 | h2,
36 | p,
37 | ul {
38 | margin: 0;
39 | }
40 |
41 | ul {
42 | padding: 0;
43 | list-style: none;
44 | }
45 |
46 | button {
47 | padding: 0.5rem 1rem;
48 | font-weight: bold;
49 | }
50 |
--------------------------------------------------------------------------------
/seo/demo/styles/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | padding: 0;
4 | margin: 0;
5 | font-family:
6 | Inter,
7 | -apple-system,
8 | BlinkMacSystemFont,
9 | Segoe UI,
10 | Roboto,
11 | Oxygen,
12 | Ubuntu,
13 | Cantarell,
14 | Fira Sans,
15 | Droid Sans,
16 | Helvetica Neue,
17 | sans-serif;
18 | }
19 |
20 | a {
21 | color: inherit;
22 | text-decoration: none;
23 | }
24 |
25 | * {
26 | box-sizing: border-box;
27 | }
28 |
29 | img {
30 | max-width: 100%;
31 | height: auto;
32 | }
33 |
34 | h1,
35 | h2,
36 | p,
37 | ul {
38 | margin: 0;
39 | }
40 |
41 | ul {
42 | padding: 0;
43 | list-style: none;
44 | }
45 |
46 | button {
47 | padding: 0.5rem 1rem;
48 | font-weight: bold;
49 | }
50 |
--------------------------------------------------------------------------------
/basics/demo/posts/pre-rendering.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Two Forms of Pre-rendering'
3 | date: '2022-01-01'
4 | ---
5 |
6 | Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.
7 |
8 | - **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
9 | - **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.
10 |
11 | Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.
12 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/login/page.tsx:
--------------------------------------------------------------------------------
1 | import AcmeLogo from '@/app/ui/acme-logo';
2 | import LoginForm from '@/app/ui/login-form';
3 | import { Suspense } from 'react';
4 |
5 | export default function LoginPage() {
6 | return (
7 |
8 |
18 |
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/basics/basics-final/posts/pre-rendering.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Two Forms of Pre-rendering'
3 | date: '2022-01-01'
4 | ---
5 |
6 | Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.
7 |
8 | - **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
9 | - **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.
10 |
11 | Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.
12 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/ui/search.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
4 |
5 | export default function Search({ placeholder }: { placeholder: string }) {
6 | return (
7 |
8 |
9 | Search
10 |
11 |
15 |
16 |
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/basics/api-routes-starter/posts/pre-rendering.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Two Forms of Pre-rendering'
3 | date: '2022-01-01'
4 | ---
5 |
6 | Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.
7 |
8 | - **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
9 | - **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.
10 |
11 | Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.
12 |
--------------------------------------------------------------------------------
/basics/typescript-final/posts/pre-rendering.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Two Forms of Pre-rendering'
3 | date: '2022-01-01'
4 | ---
5 |
6 | Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.
7 |
8 | - **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
9 | - **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.
10 |
11 | Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.
12 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 |
3 | interface ButtonProps extends React.ButtonHTMLAttributes {
4 | children: React.ReactNode;
5 | }
6 |
7 | export function Button({ children, className, ...rest }: ButtonProps) {
8 | return (
9 |
16 | {children}
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/posts/pre-rendering.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Two Forms of Pre-rendering'
3 | date: '2022-01-01'
4 | ---
5 |
6 | Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.
7 |
8 | - **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
9 | - **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.
10 |
11 | Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.
12 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/posts/pre-rendering.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'Two Forms of Pre-rendering'
3 | date: '2022-01-01'
4 | ---
5 |
6 | Next.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.
7 |
8 | - **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.
9 | - **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.
10 |
11 | Importantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a "hybrid" Next.js app by using Static Generation for most pages and using Server-side Rendering for others.
12 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/ui/button.tsx:
--------------------------------------------------------------------------------
1 | import clsx from 'clsx';
2 |
3 | interface ButtonProps extends React.ButtonHTMLAttributes {
4 | children: React.ReactNode;
5 | }
6 |
7 | export function Button({ children, className, ...rest }: ButtonProps) {
8 | return (
9 |
16 | {children}
17 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/query/route.ts:
--------------------------------------------------------------------------------
1 | import postgres from 'postgres';
2 |
3 | const sql = postgres(process.env.POSTGRES_URL!, { ssl: 'require' });
4 |
5 | async function listInvoices() {
6 | const data = await sql`
7 | SELECT invoices.amount, customers.name
8 | FROM invoices
9 | JOIN customers ON invoices.customer_id = customers.id
10 | WHERE invoices.amount = 666;
11 | `;
12 |
13 | return data;
14 | }
15 |
16 | export async function GET() {
17 | // return Response.json({
18 | // message:
19 | // 'Uncomment this file and remove this line. You can delete this file when you are finished.',
20 | // });
21 | try {
22 | return Response.json(await listInvoices());
23 | } catch (error) {
24 | return Response.json({ error }, { status: 500 });
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "lint": "eslint . --ext .cjs,.js,.jsx,.mjs,.ts,.tsx",
5 | "prettier": "prettier --write --ignore-unknown .",
6 | "prettier:check": "prettier --check --ignore-unknown .",
7 | "start": "next start",
8 | "test": "npm run lint && npm run prettier:check"
9 | },
10 | "dependencies": {
11 | "@tailwindcss/forms": "^0.5.7",
12 | "next": "^14.0.0"
13 | },
14 | "devDependencies": {
15 | "@vercel/style-guide": "^5.0.1",
16 | "eslint": "8.52.0",
17 | "eslint-config-next": "14.0.0",
18 | "eslint-config-prettier": "9.0.0",
19 | "prettier": "3.0.3",
20 | "prettier-plugin-tailwindcss": "0.5.4"
21 | },
22 | "packageManager": "pnpm@8.7.0",
23 | "engines": {
24 | "node": ">=18.17.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/dashboard/final-example/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss';
2 |
3 | const config: Config = {
4 | content: [
5 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './app/**/*.{js,ts,jsx,tsx,mdx}',
8 | ],
9 | theme: {
10 | extend: {
11 | gridTemplateColumns: {
12 | '13': 'repeat(13, minmax(0, 1fr))',
13 | },
14 | colors: {
15 | blue: {
16 | 400: '#2589FE',
17 | 500: '#0070F3',
18 | 600: '#2F6FEB',
19 | },
20 | },
21 | },
22 | keyframes: {
23 | shimmer: {
24 | '100%': {
25 | transform: 'translateX(100%)',
26 | },
27 | },
28 | },
29 | },
30 | plugins: [require('@tailwindcss/forms')],
31 | };
32 | export default config;
33 |
--------------------------------------------------------------------------------
/dashboard/starter-example/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from 'tailwindcss';
2 |
3 | const config: Config = {
4 | content: [
5 | './pages/**/*.{js,ts,jsx,tsx,mdx}',
6 | './components/**/*.{js,ts,jsx,tsx,mdx}',
7 | './app/**/*.{js,ts,jsx,tsx,mdx}',
8 | ],
9 | theme: {
10 | extend: {
11 | gridTemplateColumns: {
12 | '13': 'repeat(13, minmax(0, 1fr))',
13 | },
14 | colors: {
15 | blue: {
16 | 400: '#2589FE',
17 | 500: '#0070F3',
18 | 600: '#2F6FEB',
19 | },
20 | },
21 | },
22 | keyframes: {
23 | shimmer: {
24 | '100%': {
25 | transform: 'translateX(100%)',
26 | },
27 | },
28 | },
29 | },
30 | plugins: [require('@tailwindcss/forms')],
31 | };
32 | export default config;
33 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/query/route.ts:
--------------------------------------------------------------------------------
1 | // import postgres from 'postgres';
2 |
3 | // const sql = postgres(process.env.POSTGRES_URL!, { ssl: 'require' });
4 |
5 | // async function listInvoices() {
6 | // const data = await sql`
7 | // SELECT invoices.amount, customers.name
8 | // FROM invoices
9 | // JOIN customers ON invoices.customer_id = customers.id
10 | // WHERE invoices.amount = 666;
11 | // `;
12 |
13 | // return data;
14 | // }
15 |
16 | export async function GET() {
17 | return Response.json({
18 | message:
19 | 'Uncomment this file and remove this line. You can delete this file when you are finished.',
20 | });
21 | // try {
22 | // return Response.json(await listInvoices());
23 | // } catch (error) {
24 | // return Response.json({ error }, { status: 500 });
25 | // }
26 | }
27 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/dashboard/invoices/create/page.tsx:
--------------------------------------------------------------------------------
1 | import { fetchCustomers } from '@/app/lib/data';
2 | import Form from '@/app/ui/invoices/create-form';
3 | import Breadcrumbs from '@/app/ui/invoices/breadcrumbs';
4 | import { Metadata } from 'next';
5 |
6 | export const metadata: Metadata = {
7 | title: 'Create Invoice',
8 | };
9 |
10 | export default async function Page() {
11 | const customers = await fetchCustomers();
12 |
13 | return (
14 |
15 |
25 |
26 |
27 | );
28 | }
29 |
--------------------------------------------------------------------------------
/dashboard/final-example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "next build",
5 | "dev": "next dev --turbopack",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "@heroicons/react": "^2.2.0",
10 | "@tailwindcss/forms": "^0.5.10",
11 | "autoprefixer": "10.4.20",
12 | "bcrypt": "^5.1.1",
13 | "clsx": "^2.1.1",
14 | "next": "latest",
15 | "next-auth": "5.0.0-beta.25",
16 | "postcss": "8.5.1",
17 | "postgres": "^3.4.5",
18 | "react": "latest",
19 | "react-dom": "latest",
20 | "tailwindcss": "3.4.17",
21 | "typescript": "5.7.3",
22 | "use-debounce": "^10.0.4",
23 | "zod": "^3.24.1"
24 | },
25 | "devDependencies": {
26 | "@types/bcrypt": "^5.0.2",
27 | "@types/node": "22.10.7",
28 | "@types/react": "19.0.7",
29 | "@types/react-dom": "19.0.3"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/dashboard/final-example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "baseUrl": ".",
22 | "paths": {
23 | "@/*": ["./*"]
24 | }
25 | },
26 | "include": [
27 | "next-env.d.ts",
28 | "**/*.ts",
29 | "**/*.tsx",
30 | ".next/types/**/*.ts",
31 | "app/lib/placeholder-data.ts",
32 | "scripts/seed.js"
33 | ],
34 | "exclude": ["node_modules"]
35 | }
36 |
--------------------------------------------------------------------------------
/dashboard/starter-example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "build": "next build",
5 | "dev": "next dev --turbopack",
6 | "start": "next start"
7 | },
8 | "dependencies": {
9 | "@heroicons/react": "^2.2.0",
10 | "@tailwindcss/forms": "^0.5.10",
11 | "autoprefixer": "10.4.20",
12 | "bcrypt": "^5.1.1",
13 | "clsx": "^2.1.1",
14 | "next": "latest",
15 | "next-auth": "5.0.0-beta.25",
16 | "postcss": "8.5.1",
17 | "postgres": "^3.4.5",
18 | "react": "latest",
19 | "react-dom": "latest",
20 | "tailwindcss": "3.4.17",
21 | "typescript": "5.7.3",
22 | "use-debounce": "^10.0.4",
23 | "zod": "^3.24.1"
24 | },
25 | "devDependencies": {
26 | "@types/bcrypt": "^5.0.2",
27 | "@types/node": "22.10.7",
28 | "@types/react": "19.0.7",
29 | "@types/react-dom": "19.0.3"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/dashboard/starter-example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "baseUrl": ".",
22 | "paths": {
23 | "@/*": ["./*"]
24 | }
25 | },
26 | "include": [
27 | "next-env.d.ts",
28 | "**/*.ts",
29 | "**/*.tsx",
30 | ".next/types/**/*.ts",
31 | "app/lib/placeholder-data.ts",
32 | "scripts/seed.js"
33 | ],
34 | "exclude": ["node_modules"]
35 | }
36 |
--------------------------------------------------------------------------------
/basics/demo/styles/utils.module.css:
--------------------------------------------------------------------------------
1 | .heading2Xl {
2 | font-size: 2.5rem;
3 | line-height: 1.2;
4 | font-weight: 800;
5 | letter-spacing: -0.05rem;
6 | margin: 1rem 0;
7 | }
8 |
9 | .headingXl {
10 | font-size: 2rem;
11 | line-height: 1.3;
12 | font-weight: 800;
13 | letter-spacing: -0.05rem;
14 | margin: 1rem 0;
15 | }
16 |
17 | .headingLg {
18 | font-size: 1.5rem;
19 | line-height: 1.4;
20 | margin: 1rem 0;
21 | }
22 |
23 | .headingMd {
24 | font-size: 1.2rem;
25 | line-height: 1.5;
26 | }
27 |
28 | .borderCircle {
29 | border-radius: 9999px;
30 | }
31 |
32 | .colorInherit {
33 | color: inherit;
34 | }
35 |
36 | .padding1px {
37 | padding-top: 1px;
38 | }
39 |
40 | .list {
41 | list-style: none;
42 | padding: 0;
43 | margin: 0;
44 | }
45 |
46 | .listItem {
47 | margin: 0 0 1.25rem;
48 | }
49 |
50 | .lightText {
51 | color: #666;
52 | }
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Learn Next.js
2 |
3 | This repository contains starter templates and final code for [Learn Next.js](https://nextjs.org/learn) courses:
4 |
5 | - 🆕 [Learn Next.js App Router, Data Fetching, Databases, and Auth](https://nextjs.org/learn) ([demo](https://next-learn-dashboard.vercel.sh))
6 | - [Learn Basics and TypeScript](https://nextjs.org/learn-pages-router/basics/create-nextjs-app) ([demo](https://next-learn-starter.vercel.app))
7 | - [Learn SEO](https://nextjs.org/learn-pages-router/seo/introduction-to-seo) ([demo](https://next-seo-starter.vercel.app))
8 |
9 | ## Contributions
10 |
11 | The code for the example apps you build using Next.js Learn live in this repository and we'd be grateful for your contributions.
12 |
13 | The course curriculum is currently not open sourced, but you can [create an issue](https://github.com/vercel/next-learn/issues/new) if you find a mistake.
14 |
--------------------------------------------------------------------------------
/basics/basics-final/styles/utils.module.css:
--------------------------------------------------------------------------------
1 | .heading2Xl {
2 | font-size: 2.5rem;
3 | line-height: 1.2;
4 | font-weight: 800;
5 | letter-spacing: -0.05rem;
6 | margin: 1rem 0;
7 | }
8 |
9 | .headingXl {
10 | font-size: 2rem;
11 | line-height: 1.3;
12 | font-weight: 800;
13 | letter-spacing: -0.05rem;
14 | margin: 1rem 0;
15 | }
16 |
17 | .headingLg {
18 | font-size: 1.5rem;
19 | line-height: 1.4;
20 | margin: 1rem 0;
21 | }
22 |
23 | .headingMd {
24 | font-size: 1.2rem;
25 | line-height: 1.5;
26 | }
27 |
28 | .borderCircle {
29 | border-radius: 9999px;
30 | }
31 |
32 | .colorInherit {
33 | color: inherit;
34 | }
35 |
36 | .padding1px {
37 | padding-top: 1px;
38 | }
39 |
40 | .list {
41 | list-style: none;
42 | padding: 0;
43 | margin: 0;
44 | }
45 |
46 | .listItem {
47 | margin: 0 0 1.25rem;
48 | }
49 |
50 | .lightText {
51 | color: #666;
52 | }
53 |
--------------------------------------------------------------------------------
/dashboard/final-example/auth.config.ts:
--------------------------------------------------------------------------------
1 | import type { NextAuthConfig } from 'next-auth';
2 |
3 | export const authConfig = {
4 | pages: {
5 | signIn: '/login',
6 | },
7 | providers: [
8 | // added later in auth.ts since it requires bcrypt which is only compatible with Node.js
9 | // while this file is also used in non-Node.js environments
10 | ],
11 | callbacks: {
12 | authorized({ auth, request: { nextUrl } }) {
13 | const isLoggedIn = !!auth?.user;
14 | const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
15 | if (isOnDashboard) {
16 | if (isLoggedIn) return true;
17 | return false; // Redirect unauthenticated users to login page
18 | } else if (isLoggedIn) {
19 | return Response.redirect(new URL('/dashboard', nextUrl));
20 | }
21 | return true;
22 | },
23 | },
24 | } satisfies NextAuthConfig;
25 |
--------------------------------------------------------------------------------
/basics/api-routes-starter/styles/utils.module.css:
--------------------------------------------------------------------------------
1 | .heading2Xl {
2 | font-size: 2.5rem;
3 | line-height: 1.2;
4 | font-weight: 800;
5 | letter-spacing: -0.05rem;
6 | margin: 1rem 0;
7 | }
8 |
9 | .headingXl {
10 | font-size: 2rem;
11 | line-height: 1.3;
12 | font-weight: 800;
13 | letter-spacing: -0.05rem;
14 | margin: 1rem 0;
15 | }
16 |
17 | .headingLg {
18 | font-size: 1.5rem;
19 | line-height: 1.4;
20 | margin: 1rem 0;
21 | }
22 |
23 | .headingMd {
24 | font-size: 1.2rem;
25 | line-height: 1.5;
26 | }
27 |
28 | .borderCircle {
29 | border-radius: 9999px;
30 | }
31 |
32 | .colorInherit {
33 | color: inherit;
34 | }
35 |
36 | .padding1px {
37 | padding-top: 1px;
38 | }
39 |
40 | .list {
41 | list-style: none;
42 | padding: 0;
43 | margin: 0;
44 | }
45 |
46 | .listItem {
47 | margin: 0 0 1.25rem;
48 | }
49 |
50 | .lightText {
51 | color: #666;
52 | }
53 |
--------------------------------------------------------------------------------
/basics/typescript-final/styles/utils.module.css:
--------------------------------------------------------------------------------
1 | .heading2Xl {
2 | font-size: 2.5rem;
3 | line-height: 1.2;
4 | font-weight: 800;
5 | letter-spacing: -0.05rem;
6 | margin: 1rem 0;
7 | }
8 |
9 | .headingXl {
10 | font-size: 2rem;
11 | line-height: 1.3;
12 | font-weight: 800;
13 | letter-spacing: -0.05rem;
14 | margin: 1rem 0;
15 | }
16 |
17 | .headingLg {
18 | font-size: 1.5rem;
19 | line-height: 1.4;
20 | margin: 1rem 0;
21 | }
22 |
23 | .headingMd {
24 | font-size: 1.2rem;
25 | line-height: 1.5;
26 | }
27 |
28 | .borderCircle {
29 | border-radius: 9999px;
30 | }
31 |
32 | .colorInherit {
33 | color: inherit;
34 | }
35 |
36 | .padding1px {
37 | padding-top: 1px;
38 | }
39 |
40 | .list {
41 | list-style: none;
42 | padding: 0;
43 | margin: 0;
44 | }
45 |
46 | .listItem {
47 | margin: 0 0 1.25rem;
48 | }
49 |
50 | .lightText {
51 | color: #666;
52 | }
53 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/ui/invoices/status.tsx:
--------------------------------------------------------------------------------
1 | import { CheckIcon, ClockIcon } from '@heroicons/react/24/outline';
2 | import clsx from 'clsx';
3 |
4 | export default function InvoiceStatus({ status }: { status: string }) {
5 | return (
6 |
15 | {status === 'pending' ? (
16 | <>
17 | Pending
18 |
19 | >
20 | ) : null}
21 | {status === 'paid' ? (
22 | <>
23 | Paid
24 |
25 | >
26 | ) : null}
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/basics/data-fetching-starter/styles/utils.module.css:
--------------------------------------------------------------------------------
1 | .heading2Xl {
2 | font-size: 2.5rem;
3 | line-height: 1.2;
4 | font-weight: 800;
5 | letter-spacing: -0.05rem;
6 | margin: 1rem 0;
7 | }
8 |
9 | .headingXl {
10 | font-size: 2rem;
11 | line-height: 1.3;
12 | font-weight: 800;
13 | letter-spacing: -0.05rem;
14 | margin: 1rem 0;
15 | }
16 |
17 | .headingLg {
18 | font-size: 1.5rem;
19 | line-height: 1.4;
20 | margin: 1rem 0;
21 | }
22 |
23 | .headingMd {
24 | font-size: 1.2rem;
25 | line-height: 1.5;
26 | }
27 |
28 | .borderCircle {
29 | border-radius: 9999px;
30 | }
31 |
32 | .colorInherit {
33 | color: inherit;
34 | }
35 |
36 | .padding1px {
37 | padding-top: 1px;
38 | }
39 |
40 | .list {
41 | list-style: none;
42 | padding: 0;
43 | margin: 0;
44 | }
45 |
46 | .listItem {
47 | margin: 0 0 1.25rem;
48 | }
49 |
50 | .lightText {
51 | color: #666;
52 | }
53 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/styles/utils.module.css:
--------------------------------------------------------------------------------
1 | .heading2Xl {
2 | font-size: 2.5rem;
3 | line-height: 1.2;
4 | font-weight: 800;
5 | letter-spacing: -0.05rem;
6 | margin: 1rem 0;
7 | }
8 |
9 | .headingXl {
10 | font-size: 2rem;
11 | line-height: 1.3;
12 | font-weight: 800;
13 | letter-spacing: -0.05rem;
14 | margin: 1rem 0;
15 | }
16 |
17 | .headingLg {
18 | font-size: 1.5rem;
19 | line-height: 1.4;
20 | margin: 1rem 0;
21 | }
22 |
23 | .headingMd {
24 | font-size: 1.2rem;
25 | line-height: 1.5;
26 | }
27 |
28 | .borderCircle {
29 | border-radius: 9999px;
30 | }
31 |
32 | .colorInherit {
33 | color: inherit;
34 | }
35 |
36 | .padding1px {
37 | padding-top: 1px;
38 | }
39 |
40 | .list {
41 | list-style: none;
42 | padding: 0;
43 | margin: 0;
44 | }
45 |
46 | .listItem {
47 | margin: 0 0 1.25rem;
48 | }
49 |
50 | .lightText {
51 | color: #666;
52 | }
53 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/styles/utils.module.css:
--------------------------------------------------------------------------------
1 | .heading2Xl {
2 | font-size: 2.5rem;
3 | line-height: 1.2;
4 | font-weight: 800;
5 | letter-spacing: -0.05rem;
6 | margin: 1rem 0;
7 | }
8 |
9 | .headingXl {
10 | font-size: 2rem;
11 | line-height: 1.3;
12 | font-weight: 800;
13 | letter-spacing: -0.05rem;
14 | margin: 1rem 0;
15 | }
16 |
17 | .headingLg {
18 | font-size: 1.5rem;
19 | line-height: 1.4;
20 | margin: 1rem 0;
21 | }
22 |
23 | .headingMd {
24 | font-size: 1.2rem;
25 | line-height: 1.5;
26 | }
27 |
28 | .borderCircle {
29 | border-radius: 9999px;
30 | }
31 |
32 | .colorInherit {
33 | color: inherit;
34 | }
35 |
36 | .padding1px {
37 | padding-top: 1px;
38 | }
39 |
40 | .list {
41 | list-style: none;
42 | padding: 0;
43 | margin: 0;
44 | }
45 |
46 | .listItem {
47 | margin: 0 0 1.25rem;
48 | }
49 |
50 | .lightText {
51 | color: #666;
52 | }
53 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/ui/invoices/status.tsx:
--------------------------------------------------------------------------------
1 | import { CheckIcon, ClockIcon } from '@heroicons/react/24/outline';
2 | import clsx from 'clsx';
3 |
4 | export default function InvoiceStatus({ status }: { status: string }) {
5 | return (
6 |
15 | {status === 'pending' ? (
16 | <>
17 | Pending
18 |
19 | >
20 | ) : null}
21 | {status === 'paid' ? (
22 | <>
23 | Paid
24 |
25 | >
26 | ) : null}
27 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/dashboard/invoices/error.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { useEffect } from 'react';
4 |
5 | export default function Error({
6 | error,
7 | reset,
8 | }: {
9 | error: Error & { digest?: string };
10 | reset: () => void;
11 | }) {
12 | useEffect(() => {
13 | // Optionally log the error to an error reporting service
14 | console.error(error);
15 | }, [error]);
16 |
17 | return (
18 |
19 | Something went wrong!
20 | reset()
25 | }
26 | >
27 | Try again
28 |
29 |
30 | );
31 | }
32 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 | on: pull_request
3 | jobs:
4 | test:
5 | runs-on: ubuntu-latest
6 | steps:
7 | - name: Cancel running workflows
8 | uses: styfle/cancel-workflow-action@0.12.1
9 | with:
10 | access_token: ${{ github.token }}
11 | - name: Checkout repo
12 | uses: actions/checkout@v4
13 | - name: Setup pnpm
14 | uses: pnpm/action-setup@v3
15 | - name: Set node version
16 | uses: actions/setup-node@v3
17 | with:
18 | cache: 'pnpm'
19 | node-version: '20'
20 | - name: Cache node_modules
21 | id: node-modules-cache
22 | uses: actions/cache@v4
23 | with:
24 | path: '**/node_modules'
25 | key: node-modules-cache-${{ hashFiles('**/pnpm-lock.yaml') }}
26 | - name: Install dependencies
27 | if: steps.node-modules-cache.outputs.cache-hit != 'true'
28 | run: pnpm install
29 | - name: Run tests
30 | run: pnpm test
31 |
--------------------------------------------------------------------------------
/basics/errors/install.md:
--------------------------------------------------------------------------------
1 | # Create a Next.js App - Installation Error
2 |
3 | > Linked from https://nextjs.org/learn/basics/create-nextjs-app/setup
4 |
5 | If you see an installation error for the following installation command:
6 |
7 | ```bash
8 | npx create-next-app nextjs-blog --example "https://github.com/vercel/next-learn/tree/main/basics/learn-starter"
9 | ```
10 |
11 | Try removing everything after `nextjs-blog`:
12 |
13 | ```bash
14 | npx create-next-app nextjs-blog
15 | ```
16 |
17 | A `Could not locate the repository` error message could be the result of your workplace or school network or proxy configuration. A temporary solution may be changing your network environment by disconnecting from your workplace or school VPN, using a VPN browser extension, or trying a different wifi connection.
18 |
19 | If none of the steps above resolve your issue, please let us know in a [GitHub Issue](https://github.com/vercel/next-learn/issues) with the error text, your OS, and Node.js version (make sure your Node.js version 18 or higher).
20 |
--------------------------------------------------------------------------------
/seo/components/CodeSampleModal.js:
--------------------------------------------------------------------------------
1 | import Modal from 'react-modal';
2 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
3 | import { twilight } from 'react-syntax-highlighter/dist/cjs/styles/prism';
4 |
5 | const customStyles = {
6 | content: {
7 | top: '50%',
8 | left: '50%',
9 | right: 'auto',
10 | bottom: 'auto',
11 | marginRight: '-50%',
12 | transform: 'translate(-50%, -50%)',
13 | maxWidth: '100%',
14 | },
15 | };
16 |
17 | Modal.setAppElement('#__next');
18 |
19 | export default function CodeSampleModal({ isOpen, closeModal }) {
20 | return (
21 |
27 | Wonder no more!
28 |
29 | {`function printHelloWorld() { \n console.log('Hello World!'); \n}`}
30 |
31 | Close
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/seo/demo/components/CodeSampleModal.js:
--------------------------------------------------------------------------------
1 | import Modal from 'react-modal';
2 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
3 | import { twilight } from 'react-syntax-highlighter/dist/cjs/styles/prism';
4 |
5 | const customStyles = {
6 | content: {
7 | top: '50%',
8 | left: '50%',
9 | right: 'auto',
10 | bottom: 'auto',
11 | marginRight: '-50%',
12 | transform: 'translate(-50%, -50%)',
13 | maxWidth: '100%',
14 | },
15 | };
16 |
17 | Modal.setAppElement('#__next');
18 |
19 | export default function CodeSampleModal({ isOpen, closeModal }) {
20 | return (
21 |
27 | Wonder no more!
28 |
29 | {`function printHelloWorld() { \n console.log('Hello World!'); \n}`}
30 |
31 | Close
32 |
33 | );
34 | }
35 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/lib/posts.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import matter from 'gray-matter';
4 |
5 | const postsDirectory = path.join(process.cwd(), 'posts');
6 |
7 | export function getSortedPostsData() {
8 | // Get file names under /posts
9 | const fileNames = fs.readdirSync(postsDirectory);
10 | const allPostsData = fileNames.map((fileName) => {
11 | // Remove ".md" from file name to get id
12 | const id = fileName.replace(/\.md$/, '');
13 |
14 | // Read markdown file as string
15 | const fullPath = path.join(postsDirectory, fileName);
16 | const fileContents = fs.readFileSync(fullPath, 'utf8');
17 |
18 | // Use gray-matter to parse the post metadata section
19 | const matterResult = matter(fileContents);
20 |
21 | // Combine the data with the id
22 | return {
23 | id,
24 | ...matterResult.data,
25 | };
26 | });
27 | // Sort posts by date
28 | return allPostsData.sort((a, b) => {
29 | if (a.date < b.date) {
30 | return 1;
31 | } else {
32 | return -1;
33 | }
34 | });
35 | }
36 |
--------------------------------------------------------------------------------
/seo/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/seo/demo/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/basics/learn-starter/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/basics/demo/posts/ssg-ssr.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'When to Use Static Generation v.s. Server-side Rendering'
3 | date: '2022-01-02'
4 | ---
5 |
6 | We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.
7 |
8 | You can use Static Generation for many types of pages, including:
9 |
10 | - Marketing pages
11 | - Blog posts
12 | - E-commerce product listings
13 | - Help and documentation
14 |
15 | You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation.
16 |
17 | On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.
18 |
19 | In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data.
20 |
--------------------------------------------------------------------------------
/basics/assets-metadata-css-starter/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/basics/navigate-between-pages-starter/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
3 |
4 |
--------------------------------------------------------------------------------
/basics/basics-final/posts/ssg-ssr.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'When to Use Static Generation v.s. Server-side Rendering'
3 | date: '2022-01-02'
4 | ---
5 |
6 | We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.
7 |
8 | You can use Static Generation for many types of pages, including:
9 |
10 | - Marketing pages
11 | - Blog posts
12 | - E-commerce product listings
13 | - Help and documentation
14 |
15 | You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation.
16 |
17 | On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.
18 |
19 | In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data.
20 |
--------------------------------------------------------------------------------
/basics/typescript-final/posts/ssg-ssr.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'When to Use Static Generation v.s. Server-side Rendering'
3 | date: '2022-01-02'
4 | ---
5 |
6 | We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.
7 |
8 | You can use Static Generation for many types of pages, including:
9 |
10 | - Marketing pages
11 | - Blog posts
12 | - E-commerce product listings
13 | - Help and documentation
14 |
15 | You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation.
16 |
17 | On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.
18 |
19 | In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data.
20 |
--------------------------------------------------------------------------------
/basics/api-routes-starter/posts/ssg-ssr.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'When to Use Static Generation v.s. Server-side Rendering'
3 | date: '2022-01-02'
4 | ---
5 |
6 | We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.
7 |
8 | You can use Static Generation for many types of pages, including:
9 |
10 | - Marketing pages
11 | - Blog posts
12 | - E-commerce product listings
13 | - Help and documentation
14 |
15 | You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation.
16 |
17 | On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.
18 |
19 | In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data.
20 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/posts/ssg-ssr.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'When to Use Static Generation v.s. Server-side Rendering'
3 | date: '2022-01-02'
4 | ---
5 |
6 | We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.
7 |
8 | You can use Static Generation for many types of pages, including:
9 |
10 | - Marketing pages
11 | - Blog posts
12 | - E-commerce product listings
13 | - Help and documentation
14 |
15 | You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation.
16 |
17 | On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.
18 |
19 | In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data.
20 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/ui/invoices/breadcrumbs.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx';
2 | import Link from 'next/link';
3 | import { lusitana } from '@/app/ui/fonts';
4 |
5 | interface Breadcrumb {
6 | label: string;
7 | href: string;
8 | active?: boolean;
9 | }
10 |
11 | export default function Breadcrumbs({
12 | breadcrumbs,
13 | }: {
14 | breadcrumbs: Breadcrumb[];
15 | }) {
16 | return (
17 |
18 |
19 | {breadcrumbs.map((breadcrumb, index) => (
20 |
27 | {breadcrumb.label}
28 | {index < breadcrumbs.length - 1 ? (
29 | /
30 | ) : null}
31 |
32 | ))}
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/ui/invoices/breadcrumbs.tsx:
--------------------------------------------------------------------------------
1 | import { clsx } from 'clsx';
2 | import Link from 'next/link';
3 | import { lusitana } from '@/app/ui/fonts';
4 |
5 | interface Breadcrumb {
6 | label: string;
7 | href: string;
8 | active?: boolean;
9 | }
10 |
11 | export default function Breadcrumbs({
12 | breadcrumbs,
13 | }: {
14 | breadcrumbs: Breadcrumb[];
15 | }) {
16 | return (
17 |
18 |
19 | {breadcrumbs.map((breadcrumb, index) => (
20 |
27 | {breadcrumb.label}
28 | {index < breadcrumbs.length - 1 ? (
29 | /
30 | ) : null}
31 |
32 | ))}
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/posts/ssg-ssr.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: 'When to Use Static Generation v.s. Server-side Rendering'
3 | date: '2022-01-02'
4 | ---
5 |
6 | We recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.
7 |
8 | You can use Static Generation for many types of pages, including:
9 |
10 | - Marketing pages
11 | - Blog posts
12 | - E-commerce product listings
13 | - Help and documentation
14 |
15 | You should ask yourself: "Can I pre-render this page **ahead** of a user's request?" If the answer is yes, then you should choose Static Generation.
16 |
17 | On the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.
18 |
19 | In that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data.
20 |
--------------------------------------------------------------------------------
/basics/demo/pages/posts/[id].js:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/layout';
2 | import { getAllPostIds, getPostData } from '../../lib/posts';
3 | import Head from 'next/head';
4 | import Date from '../../components/date';
5 | import utilStyles from '../../styles/utils.module.css';
6 |
7 | export default function Post({ postData }) {
8 | return (
9 |
10 |
11 | {postData.title}
12 |
13 |
14 | {postData.title}
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | }
23 |
24 | export async function getStaticPaths() {
25 | const paths = getAllPostIds();
26 | return {
27 | paths,
28 | fallback: false,
29 | };
30 | }
31 |
32 | export async function getStaticProps({ params }) {
33 | const postData = await getPostData(params.id);
34 | return {
35 | props: {
36 | postData,
37 | },
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/basics/basics-final/pages/posts/[id].js:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/layout';
2 | import { getAllPostIds, getPostData } from '../../lib/posts';
3 | import Head from 'next/head';
4 | import Date from '../../components/date';
5 | import utilStyles from '../../styles/utils.module.css';
6 |
7 | export default function Post({ postData }) {
8 | return (
9 |
10 |
11 | {postData.title}
12 |
13 |
14 | {postData.title}
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | }
23 |
24 | export async function getStaticPaths() {
25 | const paths = getAllPostIds();
26 | return {
27 | paths,
28 | fallback: false,
29 | };
30 | }
31 |
32 | export async function getStaticProps({ params }) {
33 | const postData = await getPostData(params.id);
34 | return {
35 | props: {
36 | postData,
37 | },
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/basics/api-routes-starter/pages/posts/[id].js:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/layout';
2 | import { getAllPostIds, getPostData } from '../../lib/posts';
3 | import Head from 'next/head';
4 | import Date from '../../components/date';
5 | import utilStyles from '../../styles/utils.module.css';
6 |
7 | export default function Post({ postData }) {
8 | return (
9 |
10 |
11 | {postData.title}
12 |
13 |
14 | {postData.title}
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | }
23 |
24 | export async function getStaticPaths() {
25 | const paths = getAllPostIds();
26 | return {
27 | paths,
28 | fallback: false,
29 | };
30 | }
31 |
32 | export async function getStaticProps({ params }) {
33 | const postData = await getPostData(params.id);
34 | return {
35 | props: {
36 | postData,
37 | },
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2024 Vercel, Inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/dashboard/invoices/[id]/edit/page.tsx:
--------------------------------------------------------------------------------
1 | import Form from '@/app/ui/invoices/edit-form';
2 | import Breadcrumbs from '@/app/ui/invoices/breadcrumbs';
3 | import { fetchInvoiceById, fetchCustomers } from '@/app/lib/data';
4 | import { notFound } from 'next/navigation';
5 | import { Metadata } from 'next';
6 |
7 | export const metadata: Metadata = {
8 | title: 'Edit Invoice',
9 | };
10 |
11 | export default async function Page(props: { params: Promise<{ id: string }> }) {
12 | const params = await props.params;
13 | const id = params.id;
14 | const [invoice, customers] = await Promise.all([
15 | fetchInvoiceById(id),
16 | fetchCustomers(),
17 | ]);
18 |
19 | if (!invoice) {
20 | notFound();
21 | }
22 |
23 | return (
24 |
25 |
35 |
36 |
37 | );
38 | }
39 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/dashboard/(overview)/page.tsx:
--------------------------------------------------------------------------------
1 | import CardWrapper from '@/app/ui/dashboard/cards';
2 | import RevenueChart from '@/app/ui/dashboard/revenue-chart';
3 | import LatestInvoices from '@/app/ui/dashboard/latest-invoices';
4 | import { lusitana } from '@/app/ui/fonts';
5 | import { Suspense } from 'react';
6 | import {
7 | RevenueChartSkeleton,
8 | LatestInvoicesSkeleton,
9 | CardsSkeleton,
10 | } from '@/app/ui/skeletons';
11 |
12 | export default async function Page() {
13 | return (
14 |
15 |
16 | Dashboard
17 |
18 |
19 | }>
20 |
21 |
22 |
23 |
24 | }>
25 |
26 |
27 | }>
28 |
29 |
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/ui/dashboard/nav-links.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | UserGroupIcon,
3 | HomeIcon,
4 | DocumentDuplicateIcon,
5 | } from '@heroicons/react/24/outline';
6 |
7 | // Map of links to display in the side navigation.
8 | // Depending on the size of the application, this would be stored in a database.
9 | const links = [
10 | { name: 'Home', href: '/dashboard', icon: HomeIcon },
11 | {
12 | name: 'Invoices',
13 | href: '/dashboard/invoices',
14 | icon: DocumentDuplicateIcon,
15 | },
16 | { name: 'Customers', href: '/dashboard/customers', icon: UserGroupIcon },
17 | ];
18 |
19 | export default function NavLinks() {
20 | return (
21 | <>
22 | {links.map((link) => {
23 | const LinkIcon = link.icon;
24 | return (
25 |
30 |
31 | {link.name}
32 |
33 | );
34 | })}
35 | >
36 | );
37 | }
38 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Layout, { siteTitle } from '../components/layout';
3 | import utilStyles from '../styles/utils.module.css';
4 | import { getSortedPostsData } from '../lib/posts';
5 |
6 | export default function Home({ allPostsData }) {
7 | return (
8 |
9 |
10 | {siteTitle}
11 |
12 |
13 | [Your Self Introduction]
14 |
15 |
16 | Blog
17 |
18 | {allPostsData.map(({ id, date, title }) => (
19 |
20 | {title}
21 |
22 | {id}
23 |
24 | {date}
25 |
26 | ))}
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | export async function getStaticProps() {
34 | const allPostsData = getSortedPostsData();
35 | return {
36 | props: {
37 | allPostsData,
38 | },
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Layout, { siteTitle } from '../components/layout';
3 | import utilStyles from '../styles/utils.module.css';
4 | import { getSortedPostsData } from '../lib/posts';
5 |
6 | export default function Home({ allPostsData }) {
7 | return (
8 |
9 |
10 | {siteTitle}
11 |
12 |
13 | [Your Self Introduction]
14 |
15 |
16 | Blog
17 |
18 | {allPostsData.map(({ id, date, title }) => (
19 |
20 | {title}
21 |
22 | {id}
23 |
24 | {date}
25 |
26 | ))}
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | export async function getStaticProps() {
34 | const allPostsData = getSortedPostsData();
35 | return {
36 | props: {
37 | allPostsData,
38 | },
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/ui/invoices/buttons.tsx:
--------------------------------------------------------------------------------
1 | import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
2 | import Link from 'next/link';
3 |
4 | export function CreateInvoice() {
5 | return (
6 |
10 | Create Invoice {' '}
11 |
12 |
13 | );
14 | }
15 |
16 | export function UpdateInvoice({ id }: { id: string }) {
17 | return (
18 |
22 |
23 |
24 | );
25 | }
26 |
27 | export function DeleteInvoice({ id }: { id: string }) {
28 | return (
29 | <>
30 |
31 | Delete
32 |
33 |
34 | >
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/ui/dashboard/sidenav.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import NavLinks from '@/app/ui/dashboard/nav-links';
3 | import AcmeLogo from '@/app/ui/acme-logo';
4 | import { PowerIcon } from '@heroicons/react/24/outline';
5 |
6 | export default function SideNav() {
7 | return (
8 |
28 | );
29 | }
30 |
--------------------------------------------------------------------------------
/basics/typescript-final/pages/posts/[id].tsx:
--------------------------------------------------------------------------------
1 | import Layout from '../../components/layout';
2 | import { getAllPostIds, getPostData } from '../../lib/posts';
3 | import Head from 'next/head';
4 | import Date from '../../components/date';
5 | import utilStyles from '../../styles/utils.module.css';
6 | import { GetStaticProps, GetStaticPaths } from 'next';
7 |
8 | export default function Post({
9 | postData,
10 | }: {
11 | postData: {
12 | title: string;
13 | date: string;
14 | contentHtml: string;
15 | };
16 | }) {
17 | return (
18 |
19 |
20 | {postData.title}
21 |
22 |
23 | {postData.title}
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | export const getStaticPaths: GetStaticPaths = async () => {
34 | const paths = getAllPostIds();
35 | return {
36 | paths,
37 | fallback: false,
38 | };
39 | };
40 |
41 | export const getStaticProps: GetStaticProps = async ({ params }) => {
42 | const postData = await getPostData(params?.id as string);
43 | return {
44 | props: {
45 | postData,
46 | },
47 | };
48 | };
49 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/ui/invoices/buttons.tsx:
--------------------------------------------------------------------------------
1 | import { PencilIcon, PlusIcon, TrashIcon } from '@heroicons/react/24/outline';
2 | import Link from 'next/link';
3 | import { deleteInvoice } from '@/app/lib/actions';
4 |
5 | export function CreateInvoice() {
6 | return (
7 |
11 | Create Invoice {' '}
12 |
13 |
14 | );
15 | }
16 |
17 | export function UpdateInvoice({ id }: { id: string }) {
18 | return (
19 |
23 |
24 |
25 | );
26 | }
27 |
28 | export function DeleteInvoice({ id }: { id: string }) {
29 | const deleteInvoiceWithId = deleteInvoice.bind(null, id);
30 |
31 | return (
32 |
38 | );
39 | }
40 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/ui/dashboard/sidenav.tsx:
--------------------------------------------------------------------------------
1 | import Link from 'next/link';
2 | import NavLinks from '@/app/ui/dashboard/nav-links';
3 | import AcmeLogo from '@/app/ui/acme-logo';
4 | import { PowerIcon } from '@heroicons/react/24/outline';
5 | import { signOut } from '@/auth';
6 |
7 | export default function SideNav() {
8 | return (
9 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/dashboard/final-example/auth.ts:
--------------------------------------------------------------------------------
1 | import NextAuth from 'next-auth';
2 | import Credentials from 'next-auth/providers/credentials';
3 | import bcrypt from 'bcrypt';
4 | import postgres from 'postgres';
5 | import { z } from 'zod';
6 | import type { User } from '@/app/lib/definitions';
7 | import { authConfig } from './auth.config';
8 |
9 | const sql = postgres(process.env.POSTGRES_URL!, { ssl: 'require' });
10 |
11 | async function getUser(email: string): Promise {
12 | try {
13 | const user = await sql`SELECT * FROM users WHERE email=${email}`;
14 | return user[0];
15 | } catch (error) {
16 | console.error('Failed to fetch user:', error);
17 | throw new Error('Failed to fetch user.');
18 | }
19 | }
20 |
21 | export const { auth, signIn, signOut } = NextAuth({
22 | ...authConfig,
23 | providers: [
24 | Credentials({
25 | async authorize(credentials) {
26 | const parsedCredentials = z
27 | .object({ email: z.string().email(), password: z.string().min(6) })
28 | .safeParse(credentials);
29 |
30 | if (parsedCredentials.success) {
31 | const { email, password } = parsedCredentials.data;
32 |
33 | const user = await getUser(email);
34 | if (!user) return null;
35 |
36 | const passwordsMatch = await bcrypt.compare(password, user.password);
37 | if (passwordsMatch) return user;
38 | }
39 |
40 | console.log('Invalid credentials');
41 | return null;
42 | },
43 | }),
44 | ],
45 | });
46 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/page.tsx:
--------------------------------------------------------------------------------
1 | import AcmeLogo from '@/app/ui/acme-logo';
2 | import { ArrowRightIcon } from '@heroicons/react/24/outline';
3 | import Link from 'next/link';
4 |
5 | export default function Page() {
6 | return (
7 |
8 |
11 |
12 |
13 |
14 | Welcome to Acme. This is the example for the{' '}
15 |
16 | Next.js Learn Course
17 |
18 | , brought to you by Vercel.
19 |
20 |
24 |
Log in
25 |
26 |
27 |
28 | {/* Add Hero Images Here */}
29 |
30 |
31 |
32 | );
33 | }
34 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/ui/search.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
4 | import { usePathname, useRouter, useSearchParams } from 'next/navigation';
5 | import { useDebouncedCallback } from 'use-debounce';
6 |
7 | export default function Search({ placeholder }: { placeholder: string }) {
8 | const searchParams = useSearchParams();
9 | const { replace } = useRouter();
10 | const pathname = usePathname();
11 |
12 | const handleSearch = useDebouncedCallback((term: string) => {
13 | console.log(`Searching... ${term}`);
14 |
15 | const params = new URLSearchParams(searchParams);
16 |
17 | params.set('page', '1');
18 |
19 | if (term) {
20 | params.set('query', term);
21 | } else {
22 | params.delete('query');
23 | }
24 | replace(`${pathname}?${params.toString()}`);
25 | }, 300);
26 |
27 | return (
28 |
29 |
30 | Search
31 |
32 | {
36 | handleSearch(e.target.value);
37 | }}
38 | defaultValue={searchParams.get('query')?.toString()}
39 | />
40 |
41 |
42 | );
43 | }
44 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/ui/dashboard/nav-links.tsx:
--------------------------------------------------------------------------------
1 | 'use client';
2 |
3 | import {
4 | UserGroupIcon,
5 | HomeIcon,
6 | DocumentDuplicateIcon,
7 | } from '@heroicons/react/24/outline';
8 | import Link from 'next/link';
9 | import { usePathname } from 'next/navigation';
10 | import clsx from 'clsx';
11 |
12 | // Map of links to display in the side navigation.
13 | // Depending on the size of the application, this would be stored in a database.
14 | const links = [
15 | { name: 'Home', href: '/dashboard', icon: HomeIcon },
16 | {
17 | name: 'Invoices',
18 | href: '/dashboard/invoices',
19 | icon: DocumentDuplicateIcon,
20 | },
21 | { name: 'Customers', href: '/dashboard/customers', icon: UserGroupIcon },
22 | ];
23 |
24 | export default function NavLinks() {
25 | const pathname = usePathname();
26 |
27 | return (
28 | <>
29 | {links.map((link) => {
30 | const LinkIcon = link.icon;
31 | return (
32 |
42 |
43 | {link.name}
44 |
45 | );
46 | })}
47 | >
48 | );
49 | }
50 |
--------------------------------------------------------------------------------
/basics/basics-final/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Layout, { siteTitle } from '../components/layout';
3 | import utilStyles from '../styles/utils.module.css';
4 | import { getSortedPostsData } from '../lib/posts';
5 | import Link from 'next/link';
6 | import Date from '../components/date';
7 |
8 | export default function Home({ allPostsData }) {
9 | return (
10 |
11 |
12 | {siteTitle}
13 |
14 |
15 | [Your Self Introduction]
16 |
17 | (This is a sample website - you’ll be building a site like this in{' '}
18 | our Next.js tutorial .)
19 |
20 |
21 |
22 | Blog
23 |
24 | {allPostsData.map(({ id, date, title }) => (
25 |
26 | {title}
27 |
28 |
29 |
30 |
31 |
32 | ))}
33 |
34 |
35 |
36 | );
37 | }
38 |
39 | export async function getStaticProps() {
40 | const allPostsData = getSortedPostsData();
41 | return {
42 | props: {
43 | allPostsData,
44 | },
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/basics/api-routes-starter/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Layout, { siteTitle } from '../components/layout';
3 | import utilStyles from '../styles/utils.module.css';
4 | import { getSortedPostsData } from '../lib/posts';
5 | import Link from 'next/link';
6 | import Date from '../components/date';
7 |
8 | export default function Home({ allPostsData }) {
9 | return (
10 |
11 |
12 | {siteTitle}
13 |
14 |
15 | [Your Self Introduction]
16 |
17 | (This is a sample website - you’ll be building a site like this in{' '}
18 | our Next.js tutorial .)
19 |
20 |
21 |
22 | Blog
23 |
24 | {allPostsData.map(({ id, date, title }) => (
25 |
26 | {title}
27 |
28 |
29 |
30 |
31 |
32 | ))}
33 |
34 |
35 |
36 | );
37 | }
38 |
39 | export async function getStaticProps() {
40 | const allPostsData = getSortedPostsData();
41 | return {
42 | props: {
43 | allPostsData,
44 | },
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/ui/dashboard/cards.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BanknotesIcon,
3 | ClockIcon,
4 | UserGroupIcon,
5 | InboxIcon,
6 | } from '@heroicons/react/24/outline';
7 | import { lusitana } from '@/app/ui/fonts';
8 |
9 | const iconMap = {
10 | collected: BanknotesIcon,
11 | customers: UserGroupIcon,
12 | pending: ClockIcon,
13 | invoices: InboxIcon,
14 | };
15 |
16 | export default async function CardWrapper() {
17 | return (
18 | <>
19 | {/* NOTE: Uncomment this code in Chapter 9 */}
20 |
21 | {/*
22 |
23 |
24 | */}
29 | >
30 | );
31 | }
32 |
33 | export function Card({
34 | title,
35 | value,
36 | type,
37 | }: {
38 | title: string;
39 | value: number | string;
40 | type: 'invoices' | 'customers' | 'pending' | 'collected';
41 | }) {
42 | const Icon = iconMap[type];
43 |
44 | return (
45 |
46 |
47 | {Icon ? : null}
48 |
{title}
49 |
50 |
54 | {value}
55 |
56 |
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/basics/learn-starter/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 0 0.5rem;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | }
9 |
10 | .title a {
11 | color: #0070f3;
12 | text-decoration: none;
13 | }
14 |
15 | .title a:hover,
16 | .title a:focus,
17 | .title a:active {
18 | text-decoration: underline;
19 | }
20 |
21 | .title {
22 | margin: 0 0 1rem;
23 | line-height: 1.15;
24 | font-size: 3.6rem;
25 | }
26 |
27 | .title {
28 | text-align: center;
29 | }
30 |
31 | .title,
32 | .description {
33 | text-align: center;
34 | }
35 |
36 | .description {
37 | line-height: 1.5;
38 | font-size: 1.5rem;
39 | }
40 |
41 | .grid {
42 | display: flex;
43 | align-items: center;
44 | justify-content: center;
45 | flex-wrap: wrap;
46 |
47 | max-width: 800px;
48 | margin-top: 3rem;
49 | }
50 |
51 | .card {
52 | margin: 1rem;
53 | flex-basis: 45%;
54 | padding: 1.5rem;
55 | text-align: left;
56 | color: inherit;
57 | text-decoration: none;
58 | border: 1px solid #eaeaea;
59 | border-radius: 10px;
60 | transition:
61 | color 0.15s ease,
62 | border-color 0.15s ease;
63 | }
64 |
65 | .card:hover,
66 | .card:focus,
67 | .card:active {
68 | color: #0070f3;
69 | border-color: #0070f3;
70 | }
71 |
72 | .card h3 {
73 | margin: 0 0 1rem 0;
74 | font-size: 1.5rem;
75 | }
76 |
77 | .card p {
78 | margin: 0;
79 | font-size: 1.25rem;
80 | line-height: 1.5;
81 | }
82 |
83 | .logo {
84 | height: 1em;
85 | }
86 |
87 | @media (max-width: 600px) {
88 | .grid {
89 | width: 100%;
90 | flex-direction: column;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/dashboard/invoices/page.tsx:
--------------------------------------------------------------------------------
1 | import Pagination from '@/app/ui/invoices/pagination';
2 | import Search from '@/app/ui/search';
3 | import Table from '@/app/ui/invoices/table';
4 | import { CreateInvoice } from '@/app/ui/invoices/buttons';
5 | import { lusitana } from '@/app/ui/fonts';
6 | import { InvoicesTableSkeleton } from '@/app/ui/skeletons';
7 | import { Suspense } from 'react';
8 | import { fetchInvoicesPages } from '@/app/lib/data';
9 | import { Metadata } from 'next';
10 |
11 | export const metadata: Metadata = {
12 | title: 'Invoices',
13 | };
14 |
15 | export default async function Page(props: {
16 | searchParams?: Promise<{
17 | query?: string;
18 | page?: string;
19 | }>;
20 | }) {
21 | const searchParams = await props.searchParams;
22 | const query = searchParams?.query || '';
23 | const currentPage = Number(searchParams?.page) || 1;
24 |
25 | const totalPages = await fetchInvoicesPages(query);
26 |
27 | return (
28 |
29 |
30 |
Invoices
31 |
32 |
33 |
34 |
35 |
36 |
}>
37 |
38 |
39 |
42 |
43 | );
44 | }
45 |
--------------------------------------------------------------------------------
/basics/assets-metadata-css-starter/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 0 0.5rem;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | }
9 |
10 | .title a {
11 | color: #0070f3;
12 | text-decoration: none;
13 | }
14 |
15 | .title a:hover,
16 | .title a:focus,
17 | .title a:active {
18 | text-decoration: underline;
19 | }
20 |
21 | .title {
22 | margin: 0 0 1rem;
23 | line-height: 1.15;
24 | font-size: 3.6rem;
25 | }
26 |
27 | .title {
28 | text-align: center;
29 | }
30 |
31 | .title,
32 | .description {
33 | text-align: center;
34 | }
35 |
36 | .description {
37 | line-height: 1.5;
38 | font-size: 1.5rem;
39 | }
40 |
41 | .grid {
42 | display: flex;
43 | align-items: center;
44 | justify-content: center;
45 | flex-wrap: wrap;
46 |
47 | max-width: 800px;
48 | margin-top: 3rem;
49 | }
50 |
51 | .card {
52 | margin: 1rem;
53 | flex-basis: 45%;
54 | padding: 1.5rem;
55 | text-align: left;
56 | color: inherit;
57 | text-decoration: none;
58 | border: 1px solid #eaeaea;
59 | border-radius: 10px;
60 | transition:
61 | color 0.15s ease,
62 | border-color 0.15s ease;
63 | }
64 |
65 | .card:hover,
66 | .card:focus,
67 | .card:active {
68 | color: #0070f3;
69 | border-color: #0070f3;
70 | }
71 |
72 | .card h3 {
73 | margin: 0 0 1rem 0;
74 | font-size: 1.5rem;
75 | }
76 |
77 | .card p {
78 | margin: 0;
79 | font-size: 1.25rem;
80 | line-height: 1.5;
81 | }
82 |
83 | .logo {
84 | height: 1em;
85 | }
86 |
87 | @media (max-width: 600px) {
88 | .grid {
89 | width: 100%;
90 | flex-direction: column;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/basics/navigate-between-pages-starter/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 0 0.5rem;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | }
9 |
10 | .title a {
11 | color: #0070f3;
12 | text-decoration: none;
13 | }
14 |
15 | .title a:hover,
16 | .title a:focus,
17 | .title a:active {
18 | text-decoration: underline;
19 | }
20 |
21 | .title {
22 | margin: 0 0 1rem;
23 | line-height: 1.15;
24 | font-size: 3.6rem;
25 | }
26 |
27 | .title {
28 | text-align: center;
29 | }
30 |
31 | .title,
32 | .description {
33 | text-align: center;
34 | }
35 |
36 | .description {
37 | line-height: 1.5;
38 | font-size: 1.5rem;
39 | }
40 |
41 | .grid {
42 | display: flex;
43 | align-items: center;
44 | justify-content: center;
45 | flex-wrap: wrap;
46 |
47 | max-width: 800px;
48 | margin-top: 3rem;
49 | }
50 |
51 | .card {
52 | margin: 1rem;
53 | flex-basis: 45%;
54 | padding: 1.5rem;
55 | text-align: left;
56 | color: inherit;
57 | text-decoration: none;
58 | border: 1px solid #eaeaea;
59 | border-radius: 10px;
60 | transition:
61 | color 0.15s ease,
62 | border-color 0.15s ease;
63 | }
64 |
65 | .card:hover,
66 | .card:focus,
67 | .card:active {
68 | color: #0070f3;
69 | border-color: #0070f3;
70 | }
71 |
72 | .card h3 {
73 | margin: 0 0 1rem 0;
74 | font-size: 1.5rem;
75 | }
76 |
77 | .card p {
78 | margin: 0;
79 | font-size: 1.25rem;
80 | line-height: 1.5;
81 | }
82 |
83 | .logo {
84 | height: 1em;
85 | }
86 |
87 | @media (max-width: 600px) {
88 | .grid {
89 | width: 100%;
90 | flex-direction: column;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/lib/posts.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import matter from 'gray-matter';
4 |
5 | const postsDirectory = path.join(process.cwd(), 'posts');
6 |
7 | export function getSortedPostsData() {
8 | // Get file names under /posts
9 | const fileNames = fs.readdirSync(postsDirectory);
10 | const allPostsData = fileNames.map((fileName) => {
11 | // Remove ".md" from file name to get id
12 | const id = fileName.replace(/\.md$/, '');
13 |
14 | // Read markdown file as string
15 | const fullPath = path.join(postsDirectory, fileName);
16 | const fileContents = fs.readFileSync(fullPath, 'utf8');
17 |
18 | // Use gray-matter to parse the post metadata section
19 | const matterResult = matter(fileContents);
20 |
21 | // Combine the data with the id
22 | return {
23 | id,
24 | ...matterResult.data,
25 | };
26 | });
27 | // Sort posts by date
28 | return allPostsData.sort((a, b) => {
29 | if (a.date < b.date) {
30 | return 1;
31 | } else {
32 | return -1;
33 | }
34 | });
35 | }
36 |
37 | export function getAllPostIds() {
38 | const fileNames = fs.readdirSync(postsDirectory);
39 | return fileNames.map((fileName) => {
40 | return {
41 | params: {
42 | id: fileName.replace(/\.md$/, ''),
43 | },
44 | };
45 | });
46 | }
47 |
48 | export function getPostData(id) {
49 | const fullPath = path.join(postsDirectory, `${id}.md`);
50 | const fileContents = fs.readFileSync(fullPath, 'utf8');
51 |
52 | // Use gray-matter to parse the post metadata section
53 | const matterResult = matter(fileContents);
54 |
55 | // Combine the data with the id
56 | return {
57 | id,
58 | ...matterResult.data,
59 | };
60 | }
61 |
--------------------------------------------------------------------------------
/basics/typescript-final/pages/index.tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Layout, { siteTitle } from '../components/layout';
3 | import utilStyles from '../styles/utils.module.css';
4 | import { getSortedPostsData } from '../lib/posts';
5 | import Link from 'next/link';
6 | import Date from '../components/date';
7 | import { GetStaticProps } from 'next';
8 |
9 | export default function Home({
10 | allPostsData,
11 | }: {
12 | allPostsData: {
13 | date: string;
14 | title: string;
15 | id: string;
16 | }[];
17 | }) {
18 | return (
19 |
20 |
21 | {siteTitle}
22 |
23 |
24 | [Your Self Introduction]
25 |
26 | (This is a sample website - you’ll be building a site like this in{' '}
27 | our Next.js tutorial .)
28 |
29 |
30 |
31 | Blog
32 |
33 | {allPostsData.map(({ id, date, title }) => (
34 |
35 | {title}
36 |
37 |
38 |
39 |
40 |
41 | ))}
42 |
43 |
44 |
45 | );
46 | }
47 |
48 | export const getStaticProps: GetStaticProps = async () => {
49 | const allPostsData = getSortedPostsData();
50 | return {
51 | props: {
52 | allPostsData,
53 | },
54 | };
55 | };
56 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/ui/dashboard/cards.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | BanknotesIcon,
3 | ClockIcon,
4 | UserGroupIcon,
5 | InboxIcon,
6 | } from '@heroicons/react/24/outline';
7 | import { lusitana } from '@/app/ui/fonts';
8 | import { fetchCardData } from '@/app/lib/data';
9 |
10 | const iconMap = {
11 | collected: BanknotesIcon,
12 | customers: UserGroupIcon,
13 | pending: ClockIcon,
14 | invoices: InboxIcon,
15 | };
16 |
17 | export default async function CardWrapper() {
18 | const {
19 | numberOfInvoices,
20 | numberOfCustomers,
21 | totalPaidInvoices,
22 | totalPendingInvoices,
23 | } = await fetchCardData();
24 |
25 | return (
26 | <>
27 |
28 |
29 |
30 |
35 | >
36 | );
37 | }
38 |
39 | export function Card({
40 | title,
41 | value,
42 | type,
43 | }: {
44 | title: string;
45 | value: number | string;
46 | type: 'invoices' | 'customers' | 'pending' | 'collected';
47 | }) {
48 | const Icon = iconMap[type];
49 |
50 | return (
51 |
52 |
53 | {Icon ? : null}
54 |
{title}
55 |
56 |
60 | {value}
61 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/basics/demo/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Layout, { siteTitle } from '../components/layout';
3 | import utilStyles from '../styles/utils.module.css';
4 | import { getSortedPostsData } from '../lib/posts';
5 | import Link from 'next/link';
6 | import Date from '../components/date';
7 |
8 | export default function Home({ allPostsData }) {
9 | return (
10 |
11 |
12 | {siteTitle}
13 |
14 |
15 |
16 | Hello, I’m Shu . I’m a software engineer and a
17 | translator (English/Japanese). You can contact me on{' '}
18 | Twitter .
19 |
20 |
21 | (This is a sample website - you’ll be building a site like this in{' '}
22 | our Next.js tutorial .)
23 |
24 |
25 |
26 | Blog
27 |
28 | {allPostsData.map(({ id, date, title }) => (
29 |
30 | {title}
31 |
32 |
33 |
34 |
35 |
36 | ))}
37 |
38 |
39 |
40 | );
41 | }
42 |
43 | export async function getStaticProps() {
44 | const allPostsData = getSortedPostsData();
45 | return {
46 | props: {
47 | allPostsData,
48 | },
49 | };
50 | }
51 |
--------------------------------------------------------------------------------
/seo/countries.js:
--------------------------------------------------------------------------------
1 | export const countries = [
2 | { name: 'China', cca2: 'CN', population: 1439323776 },
3 | { name: 'India', cca2: 'IN', population: 1380004385 },
4 | { name: 'United States', cca2: 'US', population: 331002651 },
5 | { name: 'Indonesia', cca2: 'ID', population: 273523615 },
6 | { name: 'Pakistan', cca2: 'PK', population: 220892340 },
7 | { name: 'Brazil', cca2: 'BR', population: 212559417 },
8 | { name: 'Nigeria', cca2: 'NG', population: 206139589 },
9 | { name: 'Bangladesh', cca2: 'BD', population: 164689383 },
10 | { name: 'Russia', cca2: 'RU', population: 145934462 },
11 | { name: 'Mexico', cca2: 'MX', population: 128932753 },
12 | { name: 'Japan', cca2: 'JP', population: 126476461 },
13 | { name: 'Philippines', cca2: 'PH', population: 109581078 },
14 | { name: 'Egypt', cca2: 'EG', population: 102334404 },
15 | { name: 'Ethiopia', cca2: 'ET', population: 114963588 },
16 | { name: 'Vietnam', cca2: 'VN', population: 97338579 },
17 | { name: 'Germany', cca2: 'DE', population: 83783942 },
18 | { name: 'Turkey', cca2: 'TR', population: 84339067 },
19 | { name: 'Iran', cca2: 'IR', population: 83992949 },
20 | { name: 'Thailand', cca2: 'TH', population: 69799978 },
21 | { name: 'United Kingdom', cca2: 'GB', population: 67886011 },
22 | { name: 'France', cca2: 'FR', population: 65273511 },
23 | { name: 'Italy', cca2: 'IT', population: 60461826 },
24 | { name: 'South Africa', cca2: 'ZA', population: 59308690 },
25 | { name: 'Tanzania', cca2: 'TZ', population: 59734218 },
26 | { name: 'Myanmar', cca2: 'MM', population: 54409800 },
27 | { name: 'South Korea', cca2: 'KR', population: 51269185 },
28 | { name: 'Colombia', cca2: 'CO', population: 50882891 },
29 | { name: 'Kenya', cca2: 'KE', population: 53771296 },
30 | { name: 'Spain', cca2: 'ES', population: 46754778 },
31 | { name: 'Argentina', cca2: 'AR', population: 45195774 },
32 | ];
33 |
--------------------------------------------------------------------------------
/seo/demo/countries.js:
--------------------------------------------------------------------------------
1 | export const countries = [
2 | { name: 'China', cca2: 'CN', population: 1439323776 },
3 | { name: 'India', cca2: 'IN', population: 1380004385 },
4 | { name: 'United States', cca2: 'US', population: 331002651 },
5 | { name: 'Indonesia', cca2: 'ID', population: 273523615 },
6 | { name: 'Pakistan', cca2: 'PK', population: 220892340 },
7 | { name: 'Brazil', cca2: 'BR', population: 212559417 },
8 | { name: 'Nigeria', cca2: 'NG', population: 206139589 },
9 | { name: 'Bangladesh', cca2: 'BD', population: 164689383 },
10 | { name: 'Russia', cca2: 'RU', population: 145934462 },
11 | { name: 'Mexico', cca2: 'MX', population: 128932753 },
12 | { name: 'Japan', cca2: 'JP', population: 126476461 },
13 | { name: 'Philippines', cca2: 'PH', population: 109581078 },
14 | { name: 'Egypt', cca2: 'EG', population: 102334404 },
15 | { name: 'Ethiopia', cca2: 'ET', population: 114963588 },
16 | { name: 'Vietnam', cca2: 'VN', population: 97338579 },
17 | { name: 'Germany', cca2: 'DE', population: 83783942 },
18 | { name: 'Turkey', cca2: 'TR', population: 84339067 },
19 | { name: 'Iran', cca2: 'IR', population: 83992949 },
20 | { name: 'Thailand', cca2: 'TH', population: 69799978 },
21 | { name: 'United Kingdom', cca2: 'GB', population: 67886011 },
22 | { name: 'France', cca2: 'FR', population: 65273511 },
23 | { name: 'Italy', cca2: 'IT', population: 60461826 },
24 | { name: 'South Africa', cca2: 'ZA', population: 59308690 },
25 | { name: 'Tanzania', cca2: 'TZ', population: 59734218 },
26 | { name: 'Myanmar', cca2: 'MM', population: 54409800 },
27 | { name: 'South Korea', cca2: 'KR', population: 51269185 },
28 | { name: 'Colombia', cca2: 'CO', population: 50882891 },
29 | { name: 'Kenya', cca2: 'KE', population: 53771296 },
30 | { name: 'Spain', cca2: 'ES', population: 46754778 },
31 | { name: 'Argentina', cca2: 'AR', population: 45195774 },
32 | ];
33 |
--------------------------------------------------------------------------------
/seo/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 1rem 0.5rem 0;
4 | }
5 |
6 | .title a {
7 | color: #0070f3;
8 | text-decoration: none;
9 | }
10 |
11 | .title a:hover,
12 | .title a:focus,
13 | .title a:active {
14 | text-decoration: underline;
15 | }
16 |
17 | .title {
18 | margin: 0 0 1rem;
19 | line-height: 1.15;
20 | font-size: 3.6rem;
21 | }
22 |
23 | .title {
24 | text-align: center;
25 | }
26 |
27 | .heroImage {
28 | margin-bottom: 1rem;
29 | }
30 |
31 | .secondaryHeading {
32 | margin: 0 0 1rem;
33 | }
34 |
35 | .input {
36 | padding: 0.5rem;
37 | width: 300px;
38 | margin-bottom: 1rem;
39 | }
40 |
41 | .countries {
42 | display: grid;
43 | grid-gap: 1rem;
44 | }
45 |
46 | .country {
47 | border: 1px solid #000;
48 | border-radius: 0.25rem;
49 | padding: 0.25rem 0.5rem;
50 | }
51 |
52 | .codeSampleBlock {
53 | padding: 3rem 0;
54 | }
55 |
56 | .codeSampleBlock p {
57 | font-size: 1.3rem;
58 | margin-bottom: 1rem;
59 | }
60 |
61 | .footer {
62 | width: 100%;
63 | height: 100px;
64 | border-top: 1px solid #eaeaea;
65 | display: flex;
66 | justify-content: center;
67 | align-items: center;
68 | }
69 |
70 | .logo {
71 | margin-left: 0.5rem;
72 | max-width: 72px;
73 | }
74 |
75 | .footer a {
76 | display: flex;
77 | justify-content: center;
78 | align-items: center;
79 | }
80 |
81 | @media (min-width: 800px) {
82 | .countries {
83 | grid-template-columns: 1fr 1fr;
84 | }
85 | }
86 |
87 | @media (min-width: 1024px) {
88 | .heroImage {
89 | margin: 0 auto 1rem;
90 | max-width: 50vw;
91 | }
92 |
93 | .secondaryHeading {
94 | text-align: center;
95 | }
96 |
97 | .input {
98 | margin: 0 auto 1rem;
99 | display: block;
100 | }
101 |
102 | .countries {
103 | grid-template-columns: 1fr 1fr 1fr;
104 | }
105 |
106 | .codeSampleBlock {
107 | text-align: center;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/seo/demo/styles/Home.module.css:
--------------------------------------------------------------------------------
1 | .container {
2 | min-height: 100vh;
3 | padding: 1rem 0.5rem 0;
4 | }
5 |
6 | .title a {
7 | color: #0070f3;
8 | text-decoration: none;
9 | }
10 |
11 | .title a:hover,
12 | .title a:focus,
13 | .title a:active {
14 | text-decoration: underline;
15 | }
16 |
17 | .title {
18 | margin: 0 0 1rem;
19 | line-height: 1.15;
20 | font-size: 3.6rem;
21 | }
22 |
23 | .title {
24 | text-align: center;
25 | }
26 |
27 | .heroImage {
28 | margin-bottom: 1rem;
29 | }
30 |
31 | .secondaryHeading {
32 | margin: 0 0 1rem;
33 | }
34 |
35 | .input {
36 | padding: 0.5rem;
37 | width: 300px;
38 | margin-bottom: 1rem;
39 | }
40 |
41 | .countries {
42 | display: grid;
43 | grid-gap: 1rem;
44 | }
45 |
46 | .country {
47 | border: 1px solid #000;
48 | border-radius: 0.25rem;
49 | padding: 0.25rem 0.5rem;
50 | }
51 |
52 | .codeSampleBlock {
53 | padding: 3rem 0;
54 | }
55 |
56 | .codeSampleBlock p {
57 | font-size: 1.3rem;
58 | margin-bottom: 1rem;
59 | }
60 |
61 | .footer {
62 | width: 100%;
63 | height: 100px;
64 | border-top: 1px solid #eaeaea;
65 | display: flex;
66 | justify-content: center;
67 | align-items: center;
68 | }
69 |
70 | .logo {
71 | margin-left: 0.5rem;
72 | max-width: 72px;
73 | }
74 |
75 | .footer a {
76 | display: flex;
77 | justify-content: center;
78 | align-items: center;
79 | }
80 |
81 | @media (min-width: 800px) {
82 | .countries {
83 | grid-template-columns: 1fr 1fr;
84 | }
85 | }
86 |
87 | @media (min-width: 1024px) {
88 | .heroImage {
89 | margin: 0 auto 1rem;
90 | max-width: 50vw;
91 | }
92 |
93 | .secondaryHeading {
94 | text-align: center;
95 | }
96 |
97 | .input {
98 | margin: 0 auto 1rem;
99 | display: block;
100 | }
101 |
102 | .countries {
103 | grid-template-columns: 1fr 1fr 1fr;
104 | }
105 |
106 | .codeSampleBlock {
107 | text-align: center;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/basics/demo/lib/posts.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import matter from 'gray-matter';
4 | import { remark } from 'remark';
5 | import html from 'remark-html';
6 |
7 | const postsDirectory = path.join(process.cwd(), 'posts');
8 |
9 | export function getSortedPostsData() {
10 | // Get file names under /posts
11 | const fileNames = fs.readdirSync(postsDirectory);
12 | const allPostsData = fileNames.map((fileName) => {
13 | // Remove ".md" from file name to get id
14 | const id = fileName.replace(/\.md$/, '');
15 |
16 | // Read markdown file as string
17 | const fullPath = path.join(postsDirectory, fileName);
18 | const fileContents = fs.readFileSync(fullPath, 'utf8');
19 |
20 | // Use gray-matter to parse the post metadata section
21 | const matterResult = matter(fileContents);
22 |
23 | // Combine the data with the id
24 | return {
25 | id,
26 | ...matterResult.data,
27 | };
28 | });
29 | // Sort posts by date
30 | return allPostsData.sort((a, b) => {
31 | if (a.date < b.date) {
32 | return 1;
33 | } else {
34 | return -1;
35 | }
36 | });
37 | }
38 |
39 | export function getAllPostIds() {
40 | const fileNames = fs.readdirSync(postsDirectory);
41 | return fileNames.map((fileName) => {
42 | return {
43 | params: {
44 | id: fileName.replace(/\.md$/, ''),
45 | },
46 | };
47 | });
48 | }
49 |
50 | export async function getPostData(id) {
51 | const fullPath = path.join(postsDirectory, `${id}.md`);
52 | const fileContents = fs.readFileSync(fullPath, 'utf8');
53 |
54 | // Use gray-matter to parse the post metadata section
55 | const matterResult = matter(fileContents);
56 |
57 | // Use remark to convert markdown into HTML string
58 | const processedContent = await remark()
59 | .use(html)
60 | .process(matterResult.content);
61 | const contentHtml = processedContent.toString();
62 |
63 | // Combine the data with the id and contentHtml
64 | return {
65 | id,
66 | contentHtml,
67 | ...matterResult.data,
68 | };
69 | }
70 |
--------------------------------------------------------------------------------
/basics/basics-final/lib/posts.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import matter from 'gray-matter';
4 | import { remark } from 'remark';
5 | import html from 'remark-html';
6 |
7 | const postsDirectory = path.join(process.cwd(), 'posts');
8 |
9 | export function getSortedPostsData() {
10 | // Get file names under /posts
11 | const fileNames = fs.readdirSync(postsDirectory);
12 | const allPostsData = fileNames.map((fileName) => {
13 | // Remove ".md" from file name to get id
14 | const id = fileName.replace(/\.md$/, '');
15 |
16 | // Read markdown file as string
17 | const fullPath = path.join(postsDirectory, fileName);
18 | const fileContents = fs.readFileSync(fullPath, 'utf8');
19 |
20 | // Use gray-matter to parse the post metadata section
21 | const matterResult = matter(fileContents);
22 |
23 | // Combine the data with the id
24 | return {
25 | id,
26 | ...matterResult.data,
27 | };
28 | });
29 | // Sort posts by date
30 | return allPostsData.sort((a, b) => {
31 | if (a.date < b.date) {
32 | return 1;
33 | } else {
34 | return -1;
35 | }
36 | });
37 | }
38 |
39 | export function getAllPostIds() {
40 | const fileNames = fs.readdirSync(postsDirectory);
41 | return fileNames.map((fileName) => {
42 | return {
43 | params: {
44 | id: fileName.replace(/\.md$/, ''),
45 | },
46 | };
47 | });
48 | }
49 |
50 | export async function getPostData(id) {
51 | const fullPath = path.join(postsDirectory, `${id}.md`);
52 | const fileContents = fs.readFileSync(fullPath, 'utf8');
53 |
54 | // Use gray-matter to parse the post metadata section
55 | const matterResult = matter(fileContents);
56 |
57 | // Use remark to convert markdown into HTML string
58 | const processedContent = await remark()
59 | .use(html)
60 | .process(matterResult.content);
61 | const contentHtml = processedContent.toString();
62 |
63 | // Combine the data with the id and contentHtml
64 | return {
65 | id,
66 | contentHtml,
67 | ...matterResult.data,
68 | };
69 | }
70 |
--------------------------------------------------------------------------------
/basics/api-routes-starter/lib/posts.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import matter from 'gray-matter';
4 | import { remark } from 'remark';
5 | import html from 'remark-html';
6 |
7 | const postsDirectory = path.join(process.cwd(), 'posts');
8 |
9 | export function getSortedPostsData() {
10 | // Get file names under /posts
11 | const fileNames = fs.readdirSync(postsDirectory);
12 | const allPostsData = fileNames.map((fileName) => {
13 | // Remove ".md" from file name to get id
14 | const id = fileName.replace(/\.md$/, '');
15 |
16 | // Read markdown file as string
17 | const fullPath = path.join(postsDirectory, fileName);
18 | const fileContents = fs.readFileSync(fullPath, 'utf8');
19 |
20 | // Use gray-matter to parse the post metadata section
21 | const matterResult = matter(fileContents);
22 |
23 | // Combine the data with the id
24 | return {
25 | id,
26 | ...matterResult.data,
27 | };
28 | });
29 | // Sort posts by date
30 | return allPostsData.sort((a, b) => {
31 | if (a.date < b.date) {
32 | return 1;
33 | } else {
34 | return -1;
35 | }
36 | });
37 | }
38 |
39 | export function getAllPostIds() {
40 | const fileNames = fs.readdirSync(postsDirectory);
41 | return fileNames.map((fileName) => {
42 | return {
43 | params: {
44 | id: fileName.replace(/\.md$/, ''),
45 | },
46 | };
47 | });
48 | }
49 |
50 | export async function getPostData(id) {
51 | const fullPath = path.join(postsDirectory, `${id}.md`);
52 | const fileContents = fs.readFileSync(fullPath, 'utf8');
53 |
54 | // Use gray-matter to parse the post metadata section
55 | const matterResult = matter(fileContents);
56 |
57 | // Use remark to convert markdown into HTML string
58 | const processedContent = await remark()
59 | .use(html)
60 | .process(matterResult.content);
61 | const contentHtml = processedContent.toString();
62 |
63 | // Combine the data with the id and contentHtml
64 | return {
65 | id,
66 | contentHtml,
67 | ...matterResult.data,
68 | };
69 | }
70 |
--------------------------------------------------------------------------------
/basics/typescript-final/lib/posts.ts:
--------------------------------------------------------------------------------
1 | import fs from 'fs';
2 | import path from 'path';
3 | import matter from 'gray-matter';
4 | import { remark } from 'remark';
5 | import html from 'remark-html';
6 |
7 | const postsDirectory = path.join(process.cwd(), 'posts');
8 |
9 | export function getSortedPostsData() {
10 | // Get file names under /posts
11 | const fileNames = fs.readdirSync(postsDirectory);
12 | const allPostsData = fileNames.map((fileName) => {
13 | // Remove ".md" from file name to get id
14 | const id = fileName.replace(/\.md$/, '');
15 |
16 | // Read markdown file as string
17 | const fullPath = path.join(postsDirectory, fileName);
18 | const fileContents = fs.readFileSync(fullPath, 'utf8');
19 |
20 | // Use gray-matter to parse the post metadata section
21 | const matterResult = matter(fileContents);
22 |
23 | // Combine the data with the id
24 | return {
25 | id,
26 | ...(matterResult.data as { date: string; title: string }),
27 | };
28 | });
29 | // Sort posts by date
30 | return allPostsData.sort((a, b) => {
31 | if (a.date < b.date) {
32 | return 1;
33 | } else {
34 | return -1;
35 | }
36 | });
37 | }
38 |
39 | export function getAllPostIds() {
40 | const fileNames = fs.readdirSync(postsDirectory);
41 | return fileNames.map((fileName) => {
42 | return {
43 | params: {
44 | id: fileName.replace(/\.md$/, ''),
45 | },
46 | };
47 | });
48 | }
49 |
50 | export async function getPostData(id: string) {
51 | const fullPath = path.join(postsDirectory, `${id}.md`);
52 | const fileContents = fs.readFileSync(fullPath, 'utf8');
53 |
54 | // Use gray-matter to parse the post metadata section
55 | const matterResult = matter(fileContents);
56 |
57 | // Use remark to convert markdown into HTML string
58 | const processedContent = await remark()
59 | .use(html)
60 | .process(matterResult.content);
61 | const contentHtml = processedContent.toString();
62 |
63 | // Combine the data with the id and contentHtml
64 | return {
65 | id,
66 | contentHtml,
67 | ...(matterResult.data as { date: string; title: string }),
68 | };
69 | }
70 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/page.tsx:
--------------------------------------------------------------------------------
1 | import AcmeLogo from '@/app/ui/acme-logo';
2 | import { ArrowRightIcon } from '@heroicons/react/24/outline';
3 | import Link from 'next/link';
4 | import { lusitana } from '@/app/ui/fonts';
5 | import Image from 'next/image';
6 |
7 | export default function Page() {
8 | return (
9 |
10 |
13 |
14 |
15 |
18 | Welcome to Acme. This is the example for the{' '}
19 |
20 | Next.js Learn Course
21 |
22 | , brought to you by Vercel.
23 |
24 |
28 |
Log in
29 |
30 |
31 |
32 | {/* Add Hero Images Here */}
33 |
40 |
47 |
48 |
49 |
50 | );
51 | }
52 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { Revenue } from './definitions';
2 |
3 | export const formatCurrency = (amount: number) => {
4 | return (amount / 100).toLocaleString('en-US', {
5 | style: 'currency',
6 | currency: 'USD',
7 | });
8 | };
9 |
10 | export const formatDateToLocal = (
11 | dateStr: string,
12 | locale: string = 'en-US',
13 | ) => {
14 | const date = new Date(dateStr);
15 | const options: Intl.DateTimeFormatOptions = {
16 | day: 'numeric',
17 | month: 'short',
18 | year: 'numeric',
19 | };
20 | const formatter = new Intl.DateTimeFormat(locale, options);
21 | return formatter.format(date);
22 | };
23 |
24 | export const generateYAxis = (revenue: Revenue[]) => {
25 | // Calculate what labels we need to display on the y-axis
26 | // based on highest record and in 1000s
27 | const yAxisLabels = [];
28 | const highestRecord = Math.max(...revenue.map((month) => month.revenue));
29 | const topLabel = Math.ceil(highestRecord / 1000) * 1000;
30 |
31 | for (let i = topLabel; i >= 0; i -= 1000) {
32 | yAxisLabels.push(`$${i / 1000}K`);
33 | }
34 |
35 | return { yAxisLabels, topLabel };
36 | };
37 |
38 | export const generatePagination = (currentPage: number, totalPages: number) => {
39 | // If the total number of pages is 7 or less,
40 | // display all pages without any ellipsis.
41 | if (totalPages <= 7) {
42 | return Array.from({ length: totalPages }, (_, i) => i + 1);
43 | }
44 |
45 | // If the current page is among the first 3 pages,
46 | // show the first 3, an ellipsis, and the last 2 pages.
47 | if (currentPage <= 3) {
48 | return [1, 2, 3, '...', totalPages - 1, totalPages];
49 | }
50 |
51 | // If the current page is among the last 3 pages,
52 | // show the first 2, an ellipsis, and the last 3 pages.
53 | if (currentPage >= totalPages - 2) {
54 | return [1, 2, '...', totalPages - 2, totalPages - 1, totalPages];
55 | }
56 |
57 | // If the current page is somewhere in the middle,
58 | // show the first page, an ellipsis, the current page and its neighbors,
59 | // another ellipsis, and the last page.
60 | return [
61 | 1,
62 | '...',
63 | currentPage - 1,
64 | currentPage,
65 | currentPage + 1,
66 | '...',
67 | totalPages,
68 | ];
69 | };
70 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { Revenue } from './definitions';
2 |
3 | export const formatCurrency = (amount: number) => {
4 | return (amount / 100).toLocaleString('en-US', {
5 | style: 'currency',
6 | currency: 'USD',
7 | });
8 | };
9 |
10 | export const formatDateToLocal = (
11 | dateStr: string,
12 | locale: string = 'en-US',
13 | ) => {
14 | const date = new Date(dateStr);
15 | const options: Intl.DateTimeFormatOptions = {
16 | day: 'numeric',
17 | month: 'short',
18 | year: 'numeric',
19 | };
20 | const formatter = new Intl.DateTimeFormat(locale, options);
21 | return formatter.format(date);
22 | };
23 |
24 | export const generateYAxis = (revenue: Revenue[]) => {
25 | // Calculate what labels we need to display on the y-axis
26 | // based on highest record and in 1000s
27 | const yAxisLabels = [];
28 | const highestRecord = Math.max(...revenue.map((month) => month.revenue));
29 | const topLabel = Math.ceil(highestRecord / 1000) * 1000;
30 |
31 | for (let i = topLabel; i >= 0; i -= 1000) {
32 | yAxisLabels.push(`$${i / 1000}K`);
33 | }
34 |
35 | return { yAxisLabels, topLabel };
36 | };
37 |
38 | export const generatePagination = (currentPage: number, totalPages: number) => {
39 | // If the total number of pages is 7 or less,
40 | // display all pages without any ellipsis.
41 | if (totalPages <= 7) {
42 | return Array.from({ length: totalPages }, (_, i) => i + 1);
43 | }
44 |
45 | // If the current page is among the first 3 pages,
46 | // show the first 3, an ellipsis, and the last 2 pages.
47 | if (currentPage <= 3) {
48 | return [1, 2, 3, '...', totalPages - 1, totalPages];
49 | }
50 |
51 | // If the current page is among the last 3 pages,
52 | // show the first 2, an ellipsis, and the last 3 pages.
53 | if (currentPage >= totalPages - 2) {
54 | return [1, 2, '...', totalPages - 2, totalPages - 1, totalPages];
55 | }
56 |
57 | // If the current page is somewhere in the middle,
58 | // show the first page, an ellipsis, the current page and its neighbors,
59 | // another ellipsis, and the last page.
60 | return [
61 | 1,
62 | '...',
63 | currentPage - 1,
64 | currentPage,
65 | currentPage + 1,
66 | '...',
67 | totalPages,
68 | ];
69 | };
70 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/lib/definitions.ts:
--------------------------------------------------------------------------------
1 | // This file contains type definitions for your data.
2 | // It describes the shape of the data, and what data type each property should accept.
3 | // For simplicity of teaching, we're manually defining these types.
4 | // However, these types are generated automatically if you're using an ORM such as Prisma.
5 | export type User = {
6 | id: string;
7 | name: string;
8 | email: string;
9 | password: string;
10 | };
11 |
12 | export type Customer = {
13 | id: string;
14 | name: string;
15 | email: string;
16 | image_url: string;
17 | };
18 |
19 | export type Invoice = {
20 | id: string;
21 | customer_id: string;
22 | amount: number;
23 | date: string;
24 | // In TypeScript, this is called a string union type.
25 | // It means that the "status" property can only be one of the two strings: 'pending' or 'paid'.
26 | status: 'pending' | 'paid';
27 | };
28 |
29 | export type Revenue = {
30 | month: string;
31 | revenue: number;
32 | };
33 |
34 | export type LatestInvoice = {
35 | id: string;
36 | name: string;
37 | image_url: string;
38 | email: string;
39 | amount: string;
40 | };
41 |
42 | // The database returns a number for amount, but we later format it to a string with the formatCurrency function
43 | export type LatestInvoiceRaw = Omit & {
44 | amount: number;
45 | };
46 |
47 | export type InvoicesTable = {
48 | id: string;
49 | customer_id: string;
50 | name: string;
51 | email: string;
52 | image_url: string;
53 | date: string;
54 | amount: number;
55 | status: 'pending' | 'paid';
56 | };
57 |
58 | export type CustomersTableType = {
59 | id: string;
60 | name: string;
61 | email: string;
62 | image_url: string;
63 | total_invoices: number;
64 | total_pending: number;
65 | total_paid: number;
66 | };
67 |
68 | export type FormattedCustomersTable = {
69 | id: string;
70 | name: string;
71 | email: string;
72 | image_url: string;
73 | total_invoices: number;
74 | total_pending: string;
75 | total_paid: string;
76 | };
77 |
78 | export type CustomerField = {
79 | id: string;
80 | name: string;
81 | };
82 |
83 | export type InvoiceForm = {
84 | id: string;
85 | customer_id: string;
86 | amount: number;
87 | status: 'pending' | 'paid';
88 | };
89 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/lib/definitions.ts:
--------------------------------------------------------------------------------
1 | // This file contains type definitions for your data.
2 | // It describes the shape of the data, and what data type each property should accept.
3 | // For simplicity of teaching, we're manually defining these types.
4 | // However, these types are generated automatically if you're using an ORM such as Prisma.
5 | export type User = {
6 | id: string;
7 | name: string;
8 | email: string;
9 | password: string;
10 | };
11 |
12 | export type Customer = {
13 | id: string;
14 | name: string;
15 | email: string;
16 | image_url: string;
17 | };
18 |
19 | export type Invoice = {
20 | id: string;
21 | customer_id: string;
22 | amount: number;
23 | date: string;
24 | // In TypeScript, this is called a string union type.
25 | // It means that the "status" property can only be one of the two strings: 'pending' or 'paid'.
26 | status: 'pending' | 'paid';
27 | };
28 |
29 | export type Revenue = {
30 | month: string;
31 | revenue: number;
32 | };
33 |
34 | export type LatestInvoice = {
35 | id: string;
36 | name: string;
37 | image_url: string;
38 | email: string;
39 | amount: string;
40 | };
41 |
42 | // The database returns a number for amount, but we later format it to a string with the formatCurrency function
43 | export type LatestInvoiceRaw = Omit & {
44 | amount: number;
45 | };
46 |
47 | export type InvoicesTable = {
48 | id: string;
49 | customer_id: string;
50 | name: string;
51 | email: string;
52 | image_url: string;
53 | date: string;
54 | amount: number;
55 | status: 'pending' | 'paid';
56 | };
57 |
58 | export type CustomersTableType = {
59 | id: string;
60 | name: string;
61 | email: string;
62 | image_url: string;
63 | total_invoices: number;
64 | total_pending: number;
65 | total_paid: number;
66 | };
67 |
68 | export type FormattedCustomersTable = {
69 | id: string;
70 | name: string;
71 | email: string;
72 | image_url: string;
73 | total_invoices: number;
74 | total_pending: string;
75 | total_paid: string;
76 | };
77 |
78 | export type CustomerField = {
79 | id: string;
80 | name: string;
81 | };
82 |
83 | export type InvoiceForm = {
84 | id: string;
85 | customer_id: string;
86 | amount: number;
87 | status: 'pending' | 'paid';
88 | };
89 |
--------------------------------------------------------------------------------
/basics/demo/components/layout.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Image from 'next/image';
3 | import styles from './layout.module.css';
4 | import utilStyles from '../styles/utils.module.css';
5 | import Link from 'next/link';
6 |
7 | const name = 'Shu Uesugi';
8 | export const siteTitle = 'Next.js Sample Website';
9 |
10 | export default function Layout({ children, home }) {
11 | return (
12 |
13 |
14 |
15 |
19 |
25 |
26 |
27 |
28 |
29 | {home ? (
30 | <>
31 |
39 | {name}
40 | >
41 | ) : (
42 | <>
43 |
44 |
52 |
53 |
54 |
55 | {name}
56 |
57 |
58 | >
59 | )}
60 |
61 |
{children}
62 | {!home && (
63 |
64 | ← Back to home
65 |
66 | )}
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/ui/dashboard/revenue-chart.tsx:
--------------------------------------------------------------------------------
1 | import { generateYAxis } from '@/app/lib/utils';
2 | import { CalendarIcon } from '@heroicons/react/24/outline';
3 | import { lusitana } from '@/app/ui/fonts';
4 | import { fetchRevenue } from '@/app/lib/data';
5 |
6 | // This component is representational only.
7 | // For data visualization UI, check out:
8 | // https://www.tremor.so/
9 | // https://www.chartjs.org/
10 | // https://airbnb.io/visx/
11 |
12 | export default async function RevenueChart() {
13 | const revenue = await fetchRevenue();
14 |
15 | const chartHeight = 350;
16 | const { yAxisLabels, topLabel } = generateYAxis(revenue);
17 |
18 | if (!revenue || revenue.length === 0) {
19 | return No data available.
;
20 | }
21 |
22 | return (
23 |
24 |
25 | Recent Revenue
26 |
27 |
28 |
29 | {/* y-axis */}
30 |
34 | {yAxisLabels.map((label) => (
35 |
{label}
36 | ))}
37 |
38 |
39 | {revenue.map((month) => (
40 |
41 | {/* bars */}
42 |
48 | {/* x-axis */}
49 |
50 | {month.month}
51 |
52 |
53 | ))}
54 |
55 |
56 |
57 |
Last 12 months
58 |
59 |
60 |
61 | );
62 | }
63 |
--------------------------------------------------------------------------------
/basics/api-routes-starter/components/layout.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Image from 'next/image';
3 | import styles from './layout.module.css';
4 | import utilStyles from '../styles/utils.module.css';
5 | import Link from 'next/link';
6 |
7 | const name = '[Your Name]';
8 | export const siteTitle = 'Next.js Sample Website';
9 |
10 | export default function Layout({ children, home }) {
11 | return (
12 |
13 |
14 |
15 |
19 |
25 |
26 |
27 |
28 |
29 | {home ? (
30 | <>
31 |
39 | {name}
40 | >
41 | ) : (
42 | <>
43 |
44 |
52 |
53 |
54 |
55 | {name}
56 |
57 |
58 | >
59 | )}
60 |
61 |
{children}
62 | {!home && (
63 |
64 | ← Back to home
65 |
66 | )}
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/basics/data-fetching-starter/components/layout.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Image from 'next/image';
3 | import styles from './layout.module.css';
4 | import utilStyles from '../styles/utils.module.css';
5 | import Link from 'next/link';
6 |
7 | const name = '[Your Name]';
8 | export const siteTitle = 'Next.js Sample Website';
9 |
10 | export default function Layout({ children, home }) {
11 | return (
12 |
13 |
14 |
15 |
19 |
25 |
26 |
27 |
28 |
29 | {home ? (
30 | <>
31 |
39 | {name}
40 | >
41 | ) : (
42 | <>
43 |
44 |
52 |
53 |
54 |
55 | {name}
56 |
57 |
58 | >
59 | )}
60 |
61 |
{children}
62 | {!home && (
63 |
64 | ← Back to home
65 |
66 | )}
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-starter/components/layout.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Image from 'next/image';
3 | import styles from './layout.module.css';
4 | import utilStyles from '../styles/utils.module.css';
5 | import Link from 'next/link';
6 |
7 | const name = '[Your Name]';
8 | export const siteTitle = 'Next.js Sample Website';
9 |
10 | export default function Layout({ children, home }) {
11 | return (
12 |
13 |
14 |
15 |
19 |
25 |
26 |
27 |
28 |
29 | {home ? (
30 | <>
31 |
39 | {name}
40 | >
41 | ) : (
42 | <>
43 |
44 |
52 |
53 |
54 |
55 | {name}
56 |
57 |
58 | >
59 | )}
60 |
61 |
{children}
62 | {!home && (
63 |
64 | ← Back to home
65 |
66 | )}
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/basics/dynamic-routes-step-1/components/layout.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Image from 'next/image';
3 | import styles from './layout.module.css';
4 | import utilStyles from '../styles/utils.module.css';
5 | import Link from 'next/link';
6 |
7 | const name = '[Your Name]';
8 | export const siteTitle = 'Next.js Sample Website';
9 |
10 | export default function Layout({ children, home }) {
11 | return (
12 |
13 |
14 |
15 |
19 |
25 |
26 |
27 |
28 |
29 | {home ? (
30 | <>
31 |
39 | {name}
40 | >
41 | ) : (
42 | <>
43 |
44 |
52 |
53 |
54 |
55 | {name}
56 |
57 |
58 | >
59 | )}
60 |
61 |
{children}
62 | {!home && (
63 |
64 | ← Back to home
65 |
66 | )}
67 |
68 | );
69 | }
70 |
--------------------------------------------------------------------------------
/dashboard/final-example/app/ui/dashboard/latest-invoices.tsx:
--------------------------------------------------------------------------------
1 | import { ArrowPathIcon } from '@heroicons/react/24/outline';
2 | import clsx from 'clsx';
3 | import Image from 'next/image';
4 | import { lusitana } from '@/app/ui/fonts';
5 | import { fetchLatestInvoices } from '@/app/lib/data';
6 |
7 | export default async function LatestInvoices() {
8 | const latestInvoices = await fetchLatestInvoices();
9 |
10 | return (
11 |
12 |
13 | Latest Invoices
14 |
15 |
16 |
17 | {latestInvoices.map((invoice, i) => {
18 | return (
19 |
28 |
29 |
36 |
37 |
38 | {invoice.name}
39 |
40 |
41 | {invoice.email}
42 |
43 |
44 |
45 |
48 | {invoice.amount}
49 |
50 |
51 | );
52 | })}
53 |
54 |
55 |
56 |
Updated just now
57 |
58 |
59 |
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/ui/dashboard/revenue-chart.tsx:
--------------------------------------------------------------------------------
1 | import { generateYAxis } from '@/app/lib/utils';
2 | import { CalendarIcon } from '@heroicons/react/24/outline';
3 | import { lusitana } from '@/app/ui/fonts';
4 | import { Revenue } from '@/app/lib/definitions';
5 |
6 | // This component is representational only.
7 | // For data visualization UI, check out:
8 | // https://www.tremor.so/
9 | // https://www.chartjs.org/
10 | // https://airbnb.io/visx/
11 |
12 | export default async function RevenueChart({
13 | revenue,
14 | }: {
15 | revenue: Revenue[];
16 | }) {
17 | const chartHeight = 350;
18 | // NOTE: Uncomment this code in Chapter 7
19 |
20 | // const { yAxisLabels, topLabel } = generateYAxis(revenue);
21 |
22 | // if (!revenue || revenue.length === 0) {
23 | // return No data available.
;
24 | // }
25 |
26 | return (
27 |
28 |
29 | Recent Revenue
30 |
31 | {/* NOTE: Uncomment this code in Chapter 7 */}
32 |
33 | {/*
34 |
35 |
39 | {yAxisLabels.map((label) => (
40 |
{label}
41 | ))}
42 |
43 |
44 | {revenue.map((month) => (
45 |
46 |
52 |
53 | {month.month}
54 |
55 |
56 | ))}
57 |
58 |
59 |
60 |
Last 12 months
61 |
62 |
*/}
63 |
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/basics/typescript-final/components/layout.tsx:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Image from 'next/image';
3 | import styles from './layout.module.css';
4 | import utilStyles from '../styles/utils.module.css';
5 | import Link from 'next/link';
6 |
7 | const name = '[Your Name]';
8 | export const siteTitle = 'Next.js Sample Website';
9 |
10 | export default function Layout({
11 | children,
12 | home,
13 | }: {
14 | children: React.ReactNode;
15 | home?: boolean;
16 | }) {
17 | return (
18 |
19 |
20 |
21 |
25 |
31 |
32 |
33 |
34 |
35 | {home ? (
36 | <>
37 |
45 | {name}
46 | >
47 | ) : (
48 | <>
49 |
50 |
58 |
59 |
60 |
61 | {name}
62 |
63 |
64 | >
65 | )}
66 |
67 |
{children}
68 | {!home && (
69 |
70 | ← Back to home
71 |
72 | )}
73 |
74 | );
75 | }
76 |
--------------------------------------------------------------------------------
/dashboard/starter-example/app/ui/dashboard/latest-invoices.tsx:
--------------------------------------------------------------------------------
1 | import { ArrowPathIcon } from '@heroicons/react/24/outline';
2 | import clsx from 'clsx';
3 | import Image from 'next/image';
4 | import { lusitana } from '@/app/ui/fonts';
5 | import { LatestInvoice } from '@/app/lib/definitions';
6 | export default async function LatestInvoices({
7 | latestInvoices,
8 | }: {
9 | latestInvoices: LatestInvoice[];
10 | }) {
11 | return (
12 |
13 |
14 | Latest Invoices
15 |
16 |
17 | {/* NOTE: Uncomment this code in Chapter 7 */}
18 |
19 | {/*
20 | {latestInvoices.map((invoice, i) => {
21 | return (
22 |
31 |
32 |
39 |
40 |
41 | {invoice.name}
42 |
43 |
44 | {invoice.email}
45 |
46 |
47 |
48 |
51 | {invoice.amount}
52 |
53 |
54 | );
55 | })}
56 |
*/}
57 |
58 |
59 |
Updated just now
60 |
61 |
62 |
63 | );
64 | }
65 |
--------------------------------------------------------------------------------
/basics/basics-final/components/layout.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head';
2 | import Image from 'next/image';
3 | import Script from 'next/script';
4 |
5 | import styles from './layout.module.css';
6 | import utilStyles from '../styles/utils.module.css';
7 | import Link from 'next/link';
8 |
9 | const name = '[Your Name]';
10 | export const siteTitle = 'Next.js Sample Website';
11 |
12 | export default function Layout({ children, home }) {
13 | return (
14 |
15 |
16 |
17 |
21 |
27 |
28 |
29 |
30 |