diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..b02df033 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,10 @@ +module.exports = { + extends: ['next/core-web-vitals', 'prettier'], + ignorePatterns: ['**/.next/**', '**/node_modules/**'], + root: true, + settings: { + next: { + rootDir: ['basics/*/', 'dashboard/*/', 'seo/'], + }, + }, +}; diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..b18fd293 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: 'github-actions' + directory: '/' + schedule: + interval: 'weekly' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..82f19508 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,30 @@ +name: test +on: pull_request +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Cancel running workflows + uses: styfle/cancel-workflow-action@0.12.1 + with: + access_token: ${{ github.token }} + - name: Checkout repo + uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v3 + - name: Set node version + uses: actions/setup-node@v3 + with: + cache: 'pnpm' + node-version: '20' + - name: Cache node_modules + id: node-modules-cache + uses: actions/cache@v4 + with: + path: '**/node_modules' + key: node-modules-cache-${{ hashFiles('**/pnpm-lock.yaml') }} + - name: Install dependencies + if: steps.node-modules-cache.outputs.cache-hit != 'true' + run: pnpm install + - name: Run tests + run: pnpm test diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f4fa5614 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.log +.DS_Store +node_modules diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000..f239abdb --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +**/.next +**/node_modules +**/package-lock.json +**/pnpm-lock.yaml diff --git a/README.md b/README.md index 7fa61235..dba63fb1 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,13 @@ # Learn Next.js -This repository contains starter templates for [Learn Next.js](https://nextjs.org/learn) courses: +This repository contains starter templates and final code for [Learn Next.js](https://nextjs.org/learn) courses: -- [Learn Basics and TypeScript](https://nextjs.org/learn/basics/create-nextjs-app) -- [Learn SEO](https://nextjs.org/learn/seo/introduction-to-seo) +- 🆕 [Learn Next.js App Router, Data Fetching, Databases, and Auth](https://nextjs.org/learn) ([demo](https://next-learn-dashboard.vercel.sh)) +- [Learn Basics and TypeScript](https://nextjs.org/learn-pages-router/basics/create-nextjs-app) ([demo](https://next-learn-starter.vercel.app)) +- [Learn SEO](https://nextjs.org/learn-pages-router/seo/introduction-to-seo) ([demo](https://next-seo-starter.vercel.app)) -The final examples are available on: +## Contributions -- https://next-learn-starter.vercel.app -- https://next-seo-starter.vercel.app +The code for the example apps you build using Next.js Learn live in this repository and we'd be grateful for your contributions. + +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. diff --git a/basics/.gitignore b/basics/.gitignore deleted file mode 100644 index 35dcb160..00000000 --- a/basics/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.next -node_modules -*.log -yarn.lock -package-lock.json \ No newline at end of file diff --git a/basics/.prettierignore b/basics/.prettierignore deleted file mode 100644 index f74c7818..00000000 --- a/basics/.prettierignore +++ /dev/null @@ -1,2 +0,0 @@ -.next -node_modules diff --git a/basics/.prettierrc b/basics/.prettierrc deleted file mode 100644 index dcaa31d5..00000000 --- a/basics/.prettierrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "singleQuote": true, - "semi": false, - "trailingComma": "none", - "arrowParens": "avoid", - "endOfLine": "auto" -} diff --git a/basics/api-routes-starter/.nvmrc b/basics/api-routes-starter/.nvmrc new file mode 100644 index 00000000..3c032078 --- /dev/null +++ b/basics/api-routes-starter/.nvmrc @@ -0,0 +1 @@ +18 diff --git a/basics/api-routes-starter/README.md b/basics/api-routes-starter/README.md index 02695bc1..1a60afbe 100644 --- a/basics/api-routes-starter/README.md +++ b/basics/api-routes-starter/README.md @@ -1 +1 @@ -This is a starter template for [Learn Next.js](https://nextjs.org/learn). \ No newline at end of file +This is a starter template for [Learn Next.js](https://nextjs.org/learn). diff --git a/basics/api-routes-starter/components/date.js b/basics/api-routes-starter/components/date.js index 0ec71d37..f9e5891a 100644 --- a/basics/api-routes-starter/components/date.js +++ b/basics/api-routes-starter/components/date.js @@ -1,6 +1,6 @@ -import { parseISO, format } from 'date-fns' +import { parseISO, format } from 'date-fns'; export default function Date({ dateString }) { - const date = parseISO(dateString) - return + const date = parseISO(dateString); + return ; } diff --git a/basics/api-routes-starter/components/layout.js b/basics/api-routes-starter/components/layout.js index 92f0214c..21f7ada6 100644 --- a/basics/api-routes-starter/components/layout.js +++ b/basics/api-routes-starter/components/layout.js @@ -1,11 +1,11 @@ -import Head from 'next/head' -import Image from 'next/image' -import styles from './layout.module.css' -import utilStyles from '../styles/utils.module.css' -import Link from 'next/link' +import Head from 'next/head'; +import Image from 'next/image'; +import styles from './layout.module.css'; +import utilStyles from '../styles/utils.module.css'; +import Link from 'next/link'; -const name = '[Your Name]' -export const siteTitle = 'Next.js Sample Website' +const name = '[Your Name]'; +export const siteTitle = 'Next.js Sample Website'; export default function Layout({ children, home }) { return ( @@ -19,7 +19,7 @@ export default function Layout({ children, home }) { @@ -41,20 +41,18 @@ export default function Layout({ children, home }) { ) : ( <> - - {name} - + {name}

- - {name} + + {name}

@@ -63,11 +61,9 @@ export default function Layout({ children, home }) {
{children}
{!home && (
- - ← Back to home - + ← Back to home
)} - ) + ); } diff --git a/basics/api-routes-starter/lib/posts.js b/basics/api-routes-starter/lib/posts.js index be54bb2a..f32c755b 100644 --- a/basics/api-routes-starter/lib/posts.js +++ b/basics/api-routes-starter/lib/posts.js @@ -1,69 +1,69 @@ -import fs from 'fs' -import path from 'path' -import matter from 'gray-matter' -import { remark } from 'remark' -import html from 'remark-html' +import fs from 'fs'; +import path from 'path'; +import matter from 'gray-matter'; +import { remark } from 'remark'; +import html from 'remark-html'; -const postsDirectory = path.join(process.cwd(), 'posts') +const postsDirectory = path.join(process.cwd(), 'posts'); export function getSortedPostsData() { // Get file names under /posts - const fileNames = fs.readdirSync(postsDirectory) - const allPostsData = fileNames.map(fileName => { + const fileNames = fs.readdirSync(postsDirectory); + const allPostsData = fileNames.map((fileName) => { // Remove ".md" from file name to get id - const id = fileName.replace(/\.md$/, '') + const id = fileName.replace(/\.md$/, ''); // Read markdown file as string - const fullPath = path.join(postsDirectory, fileName) - const fileContents = fs.readFileSync(fullPath, 'utf8') + const fullPath = path.join(postsDirectory, fileName); + const fileContents = fs.readFileSync(fullPath, 'utf8'); // Use gray-matter to parse the post metadata section - const matterResult = matter(fileContents) + const matterResult = matter(fileContents); // Combine the data with the id return { id, - ...matterResult.data - } - }) + ...matterResult.data, + }; + }); // Sort posts by date return allPostsData.sort((a, b) => { if (a.date < b.date) { - return 1 + return 1; } else { - return -1 + return -1; } - }) + }); } export function getAllPostIds() { - const fileNames = fs.readdirSync(postsDirectory) - return fileNames.map(fileName => { + const fileNames = fs.readdirSync(postsDirectory); + return fileNames.map((fileName) => { return { params: { - id: fileName.replace(/\.md$/, '') - } - } - }) + id: fileName.replace(/\.md$/, ''), + }, + }; + }); } export async function getPostData(id) { - const fullPath = path.join(postsDirectory, `${id}.md`) - const fileContents = fs.readFileSync(fullPath, 'utf8') + const fullPath = path.join(postsDirectory, `${id}.md`); + const fileContents = fs.readFileSync(fullPath, 'utf8'); // Use gray-matter to parse the post metadata section - const matterResult = matter(fileContents) + const matterResult = matter(fileContents); // Use remark to convert markdown into HTML string const processedContent = await remark() .use(html) - .process(matterResult.content) - const contentHtml = processedContent.toString() + .process(matterResult.content); + const contentHtml = processedContent.toString(); // Combine the data with the id and contentHtml return { id, contentHtml, - ...matterResult.data - } + ...matterResult.data, + }; } diff --git a/basics/api-routes-starter/package.json b/basics/api-routes-starter/package.json index dde93f34..aae3a0eb 100644 --- a/basics/api-routes-starter/package.json +++ b/basics/api-routes-starter/package.json @@ -1,17 +1,20 @@ { "private": true, "scripts": { - "dev": "next dev", "build": "next build", + "dev": "next dev --turbopack", "start": "next start" }, "dependencies": { - "date-fns": "^2.11.1", - "gray-matter": "^4.0.2", + "date-fns": "^2.29.3", + "gray-matter": "^4.0.3", "next": "latest", - "react": "17.0.2", - "react-dom": "17.0.2", - "remark": "^14.0.1", - "remark-html": "^15.0.0" + "react": "latest", + "react-dom": "latest", + "remark": "^14.0.2", + "remark-html": "^15.0.1" + }, + "engines": { + "node": ">=18" } } diff --git a/basics/api-routes-starter/pages/_app.js b/basics/api-routes-starter/pages/_app.js index 7e66efca..b0ac7435 100644 --- a/basics/api-routes-starter/pages/_app.js +++ b/basics/api-routes-starter/pages/_app.js @@ -1,5 +1,5 @@ -import '../styles/global.css' +import '../styles/global.css'; export default function App({ Component, pageProps }) { - return + return ; } diff --git a/basics/api-routes-starter/pages/index.js b/basics/api-routes-starter/pages/index.js index f09aed30..677dc819 100644 --- a/basics/api-routes-starter/pages/index.js +++ b/basics/api-routes-starter/pages/index.js @@ -1,9 +1,9 @@ -import Head from 'next/head' -import Layout, { siteTitle } from '../components/layout' -import utilStyles from '../styles/utils.module.css' -import { getSortedPostsData } from '../lib/posts' -import Link from 'next/link' -import Date from '../components/date' +import Head from 'next/head'; +import Layout, { siteTitle } from '../components/layout'; +import utilStyles from '../styles/utils.module.css'; +import { getSortedPostsData } from '../lib/posts'; +import Link from 'next/link'; +import Date from '../components/date'; export default function Home({ allPostsData }) { return ( @@ -23,9 +23,7 @@ export default function Home({ allPostsData }) { - ) + ); } export async function getStaticProps() { - const allPostsData = getSortedPostsData() + const allPostsData = getSortedPostsData(); return { props: { - allPostsData - } - } + allPostsData, + }, + }; } diff --git a/basics/api-routes-starter/pages/posts/[id].js b/basics/api-routes-starter/pages/posts/[id].js index 28faaad5..efc338e8 100644 --- a/basics/api-routes-starter/pages/posts/[id].js +++ b/basics/api-routes-starter/pages/posts/[id].js @@ -1,8 +1,8 @@ -import Layout from '../../components/layout' -import { getAllPostIds, getPostData } from '../../lib/posts' -import Head from 'next/head' -import Date from '../../components/date' -import utilStyles from '../../styles/utils.module.css' +import Layout from '../../components/layout'; +import { getAllPostIds, getPostData } from '../../lib/posts'; +import Head from 'next/head'; +import Date from '../../components/date'; +import utilStyles from '../../styles/utils.module.css'; export default function Post({ postData }) { return ( @@ -18,22 +18,22 @@ export default function Post({ postData }) {
- ) + ); } export async function getStaticPaths() { - const paths = getAllPostIds() + const paths = getAllPostIds(); return { paths, - fallback: false - } + fallback: false, + }; } export async function getStaticProps({ params }) { - const postData = await getPostData(params.id) + const postData = await getPostData(params.id); return { props: { - postData - } - } + postData, + }, + }; } diff --git a/basics/api-routes-starter/posts/pre-rendering.md b/basics/api-routes-starter/posts/pre-rendering.md index de33130f..3bf0c3ee 100644 --- a/basics/api-routes-starter/posts/pre-rendering.md +++ b/basics/api-routes-starter/posts/pre-rendering.md @@ -1,6 +1,6 @@ --- -title: "Two Forms of Pre-rendering" -date: "2020-01-01" +title: 'Two Forms of Pre-rendering' +date: '2022-01-01' --- 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. @@ -8,4 +8,4 @@ Next.js has two forms of pre-rendering: **Static Generation** and **Server-side - **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request. - **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**. -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. \ No newline at end of file +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. diff --git a/basics/api-routes-starter/posts/ssg-ssr.md b/basics/api-routes-starter/posts/ssg-ssr.md index fe4a156a..b670ac3e 100644 --- a/basics/api-routes-starter/posts/ssg-ssr.md +++ b/basics/api-routes-starter/posts/ssg-ssr.md @@ -1,6 +1,6 @@ --- -title: "When to Use Static Generation v.s. Server-side Rendering" -date: "2020-01-02" +title: 'When to Use Static Generation v.s. Server-side Rendering' +date: '2022-01-02' --- 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. @@ -16,4 +16,4 @@ You should ask yourself: "Can I pre-render this page **ahead** of a user's reque 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. -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. \ No newline at end of file +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. diff --git a/basics/api-routes-starter/styles/global.css b/basics/api-routes-starter/styles/global.css index 9e1b0fbc..c3509347 100644 --- a/basics/api-routes-starter/styles/global.css +++ b/basics/api-routes-starter/styles/global.css @@ -2,8 +2,18 @@ html, body { padding: 0; margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, - Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-family: + -apple-system, + BlinkMacSystemFont, + Segoe UI, + Roboto, + Oxygen, + Ubuntu, + Cantarell, + Fira Sans, + Droid Sans, + Helvetica Neue, + sans-serif; line-height: 1.6; font-size: 18px; } diff --git a/basics/assets-metadata-css-starter/.nvmrc b/basics/assets-metadata-css-starter/.nvmrc new file mode 100644 index 00000000..3c032078 --- /dev/null +++ b/basics/assets-metadata-css-starter/.nvmrc @@ -0,0 +1 @@ +18 diff --git a/basics/assets-metadata-css-starter/README.md b/basics/assets-metadata-css-starter/README.md index 02695bc1..1a60afbe 100644 --- a/basics/assets-metadata-css-starter/README.md +++ b/basics/assets-metadata-css-starter/README.md @@ -1 +1 @@ -This is a starter template for [Learn Next.js](https://nextjs.org/learn). \ No newline at end of file +This is a starter template for [Learn Next.js](https://nextjs.org/learn). diff --git a/basics/assets-metadata-css-starter/package.json b/basics/assets-metadata-css-starter/package.json index 9e4441a0..159e2344 100644 --- a/basics/assets-metadata-css-starter/package.json +++ b/basics/assets-metadata-css-starter/package.json @@ -1,13 +1,16 @@ { "private": true, "scripts": { - "dev": "next dev", "build": "next build", + "dev": "next dev --turbopack", "start": "next start" }, "dependencies": { "next": "latest", - "react": "17.0.2", - "react-dom": "17.0.2" + "react": "latest", + "react-dom": "latest" + }, + "engines": { + "node": ">=18" } } diff --git a/basics/assets-metadata-css-starter/pages/index.js b/basics/assets-metadata-css-starter/pages/index.js index a245ddc8..47efa0fe 100644 --- a/basics/assets-metadata-css-starter/pages/index.js +++ b/basics/assets-metadata-css-starter/pages/index.js @@ -1,41 +1,38 @@ -import Link from 'next/link' - -import Head from 'next/head' +import Link from 'next/link'; +import Head from 'next/head'; +import styles from '../styles/Home.module.css'; export default function Home() { return ( -
+
Create Next App
-

- Read{' '} - - this page! - +

+ Read this page!

-

+

Get started by editing pages/index.js

-
- +
+

Documentation →

Find in-depth information about Next.js features and API.

- +

Learn →

Learn about Next.js in an interactive course with quizzes!

Examples →

Discover and deploy boilerplate example Next.js projects.

@@ -43,7 +40,7 @@ export default function Home() {

Deploy →

@@ -60,20 +57,11 @@ export default function Home() { rel="noopener noreferrer" > Powered by{' '} - Vercel Logo + Vercel @@ -200,15 +110,23 @@ export default function Home() { body { padding: 0; margin: 0; - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, - Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, + font-family: + -apple-system, + BlinkMacSystemFont, + Segoe UI, + Roboto, + Oxygen, + Ubuntu, + Cantarell, + Fira Sans, + Droid Sans, + Helvetica Neue, sans-serif; } - * { box-sizing: border-box; } `}

- ) + ); } diff --git a/basics/assets-metadata-css-starter/pages/posts/first-post.js b/basics/assets-metadata-css-starter/pages/posts/first-post.js index 7f840e53..afd8c681 100644 --- a/basics/assets-metadata-css-starter/pages/posts/first-post.js +++ b/basics/assets-metadata-css-starter/pages/posts/first-post.js @@ -1,14 +1,12 @@ -import Link from 'next/link' +import Link from 'next/link'; export default function FirstPost() { return ( <>

First Post

- - Back to home - + Back to home

- ) + ); } diff --git a/basics/assets-metadata-css-starter/styles/Home.module.css b/basics/assets-metadata-css-starter/styles/Home.module.css new file mode 100644 index 00000000..cb4a9762 --- /dev/null +++ b/basics/assets-metadata-css-starter/styles/Home.module.css @@ -0,0 +1,92 @@ +.container { + min-height: 100vh; + padding: 0 0.5rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.title a { + color: #0070f3; + text-decoration: none; +} + +.title a:hover, +.title a:focus, +.title a:active { + text-decoration: underline; +} + +.title { + margin: 0 0 1rem; + line-height: 1.15; + font-size: 3.6rem; +} + +.title { + text-align: center; +} + +.title, +.description { + text-align: center; +} + +.description { + line-height: 1.5; + font-size: 1.5rem; +} + +.grid { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + + max-width: 800px; + margin-top: 3rem; +} + +.card { + margin: 1rem; + flex-basis: 45%; + padding: 1.5rem; + text-align: left; + color: inherit; + text-decoration: none; + border: 1px solid #eaeaea; + border-radius: 10px; + transition: + color 0.15s ease, + border-color 0.15s ease; +} + +.card:hover, +.card:focus, +.card:active { + color: #0070f3; + border-color: #0070f3; +} + +.card h3 { + margin: 0 0 1rem 0; + font-size: 1.5rem; +} + +.card p { + margin: 0; + font-size: 1.25rem; + line-height: 1.5; +} + +.logo { + height: 1em; +} + +@media (max-width: 600px) { + .grid { + width: 100%; + flex-direction: column; + } +} diff --git a/basics/assets-metadata-css-starter/styles/global.css b/basics/assets-metadata-css-starter/styles/global.css new file mode 100644 index 00000000..a5d1aa50 --- /dev/null +++ b/basics/assets-metadata-css-starter/styles/global.css @@ -0,0 +1,32 @@ +html, +body { + padding: 0; + margin: 0; + font-family: + Inter, + -apple-system, + BlinkMacSystemFont, + Segoe UI, + Roboto, + Oxygen, + Ubuntu, + Cantarell, + Fira Sans, + Droid Sans, + Helvetica Neue, + sans-serif; +} + +a { + color: inherit; + text-decoration: none; +} + +* { + box-sizing: border-box; +} + +img { + max-width: 100%; + height: auto; +} diff --git a/basics/basics-final/.nvmrc b/basics/basics-final/.nvmrc new file mode 100644 index 00000000..3c032078 --- /dev/null +++ b/basics/basics-final/.nvmrc @@ -0,0 +1 @@ +18 diff --git a/basics/basics-final/README.md b/basics/basics-final/README.md index 02695bc1..1a60afbe 100644 --- a/basics/basics-final/README.md +++ b/basics/basics-final/README.md @@ -1 +1 @@ -This is a starter template for [Learn Next.js](https://nextjs.org/learn). \ No newline at end of file +This is a starter template for [Learn Next.js](https://nextjs.org/learn). diff --git a/basics/basics-final/components/date.js b/basics/basics-final/components/date.js index 0ec71d37..f9e5891a 100644 --- a/basics/basics-final/components/date.js +++ b/basics/basics-final/components/date.js @@ -1,6 +1,6 @@ -import { parseISO, format } from 'date-fns' +import { parseISO, format } from 'date-fns'; export default function Date({ dateString }) { - const date = parseISO(dateString) - return + const date = parseISO(dateString); + return ; } diff --git a/basics/basics-final/components/layout.js b/basics/basics-final/components/layout.js index 92f0214c..821d0434 100644 --- a/basics/basics-final/components/layout.js +++ b/basics/basics-final/components/layout.js @@ -1,11 +1,13 @@ -import Head from 'next/head' -import Image from 'next/image' -import styles from './layout.module.css' -import utilStyles from '../styles/utils.module.css' -import Link from 'next/link' +import Head from 'next/head'; +import Image from 'next/image'; +import Script from 'next/script'; -const name = '[Your Name]' -export const siteTitle = 'Next.js Sample Website' +import styles from './layout.module.css'; +import utilStyles from '../styles/utils.module.css'; +import Link from 'next/link'; + +const name = '[Your Name]'; +export const siteTitle = 'Next.js Sample Website'; export default function Layout({ children, home }) { return ( @@ -19,12 +21,19 @@ export default function Layout({ children, home }) { +