[examples] Add portfolio starter kit. (#23212)

This commit is contained in:
Lee Robinson 2021-03-21 19:44:59 -05:00 committed by GitHub
parent 89ec21ed68
commit 6a418b7da9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 719 additions and 0 deletions

36
examples/blog/.gitignore vendored Normal file
View file

@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
# vercel
.vercel
public/feed.xml

36
examples/blog/README.md Normal file
View file

@ -0,0 +1,36 @@
# Portfolio Starter Kit
This portfolio is built with **Next.js** and a library called [Nextra](https://nextra.vercel.app/). It allows you to write Markdown and focus on the _content_ of your portfolio. This starter includes:
- Automatically configured to handle Markdown/MDX
- Generates an RSS feed based on your posts
- A beautiful theme included out of the box
- Easily categorize posts with tags
- Fast, optimized web font loading
https://demo.vercel.blog
## Configuration
1. Update your name in `theme.config.js` or change the footer.
1. Update your name and site URL for the RSS feed in `scripts/gen-rss.js`.
1. Update the meta tags in `pages/_document.js`.
1. Update the posts inside `pages/posts/*.md` with your own content.
## Deploy your own
Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/blog&project-name=portfolio&repository-name=portfolio)
## How to use
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
```bash
npx create-next-app --example blog my-blog
# or
yarn create next-app --example blog my-blog
```
Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).

View file

@ -0,0 +1,2 @@
const withNextra = require('nextra')('nextra-theme-blog', './theme.config.js')
module.exports = withNextra()

View file

@ -0,0 +1,27 @@
{
"name": "portfolio",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"dev": "next",
"build": "node ./scripts/gen-rss.js && next build",
"start": "next start"
},
"dependencies": {
"gray-matter": "^4.0.2",
"next": "latest",
"nextra": "^0.4.3",
"nextra-theme-blog": "^0.1.4",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"rss": "^1.2.2"
},
"prettier": {
"arrowParens": "always",
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "none",
"semi": false
}
}

View file

@ -0,0 +1,27 @@
import 'nextra-theme-blog/style.css'
import Head from 'next/head'
import '../styles/main.css'
export default function Nextra({ Component, pageProps }) {
return (
<>
<Head>
<link
rel="alternate"
type="application/rss+xml"
title="RSS"
href="/feed.xml"
/>
<link
rel="preload"
href="/fonts/Inter-roman.latin.var.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
</Head>
<Component {...pageProps} />
</>
)
}

View file

@ -0,0 +1,41 @@
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
const meta = {
title: 'Next.js Blog Starter Kit',
description: 'Clone and deploy your own Next.js portfolio in minutes.',
image:
'https://assets.vercel.com/image/upload/q_auto/front/vercel/dps.png'
}
return (
<Html>
<Head>
<meta name="robots" content="follow, index" />
<meta name="description" content={meta.description} />
<meta property="og:site_name" content={meta.title} />
<meta property="og:description" content={meta.description} />
<meta property="og:title" content={meta.title} />
<meta property="og:image" content={meta.image} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@yourname" />
<meta name="twitter:title" content={meta.title} />
<meta name="twitter:description" content={meta.description} />
<meta name="twitter:image" content={meta.image} />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument

View file

@ -0,0 +1,46 @@
---
type: page
title: About
date: 2021-03-19
---
# Your Name
Hey, I'm a Senior Software Engineer at Company. I enjoy working with Next.js and crafting beautiful front-end experiences.
This portfolio is built with **Next.js** and a library called [Nextra](https://nextra.vercel.app/). It allows you to write Markdown and focus on the _content_ of your portfolio.
[**Deploy your own**](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/blog&project-name=portfolio&repository-name=portfolio) in a few minutes.
---
<div>
Twitter [@yourname](https://twitter.com/yourname)
<br />
GitHub [@yourname](https://github.com/yourname)
<br />
Instagram [@yourname](https://instagram.com/yourname)
<br />
Email your@name.com
</div>
<br />
### Experience
- Senior Software Engineer at Company 2021
- Software Engineer at Company, 20172021
- Bachelor of Computer Science at Your University, 20132017
### Projects
- [Next.js](https://nextjs.org)
- [Nextra](https://nextra.vercel.app/)
- [Vercel](http://vercel.com)
## Skills
- Next.js
- TypeScript
- Vercel
- CSS

View file

@ -0,0 +1,31 @@
---
type: page
title: Photos
date: 2021-03-18
---
# Photos
Here's some of my photography.
import Image from 'next/image'
<Image
src="/images/photo2.jpg"
alt="Photo"
width={1125}
height={750}
quality={100}
className="next-image"
/>
[Unsplash ↗ ](https://unsplash.com/photos/WeYamle9fDM)
<Image
src="/images/photo.jpg"
alt="Photo"
width={1125}
height={750}
quality={100}
className="next-image"
/>
[Unsplash ↗ ](https://unsplash.com/photos/ndN00KmbJ1c)

View file

@ -0,0 +1,7 @@
---
type: posts
title: Posts
date: 2021-03-18
---
# Posts

View file

@ -0,0 +1,99 @@
---
title: Markdown Examples
date: 2021/3/19
description: View examples of all possible Markdown options.
tag: web development
author: You
---
# Markdown Examples
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Emphasis
**This is bold text**
_This is italic text_
~~Strikethrough~~
## Blockquotes
> Develop. Preview. Ship. Vercel
## Lists
Unordered
- Lorem ipsum dolor sit amet
- Consectetur adipiscing elit
- Integer molestie lorem at massa
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
## Code
Inline `code`
```
export default function Nextra({ Component, pageProps }) {
return (
<>
<Head>
<link
rel="alternate"
type="application/rss+xml"
title="RSS"
href="/feed.xml"
/>
<link
rel="preload"
href="/fonts/Inter-roman.latin.var.woff2"
as="font"
type="font/woff2"
crossOrigin="anonymous"
/>
</Head>
<Component {...pageProps} />
</>
)
}
```
## Tables
| **Option** | **Description** |
| ---------- | --------------------------------------------------------------------------------------------------------------------------- |
| First | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. |
| Second | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. |
| Third | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. |
## Links
- [Next.js](https://nextjs.org)
- [Nextra](https://nextra.vercel.app/)
- [Vercel](http://vercel.com)
### Footnotes
- Footnote [^1].
- Footnote [^2].
[^1]: Footnote **can have markup**
and multiple paragraphs.
[^2]: Footnote text.

View file

@ -0,0 +1,238 @@
---
title: Next.js Pages
date: 2021/3/18
description: Learn more about Next.js pages.
tag: web development
author: You
---
# Next.js Pages
In Next.js, a **page** is a [React Component](https://reactjs.org/docs/components-and-props.html) exported from a `.js`, `.jsx`, `.ts`, or `.tsx` file in the `pages` directory. Each page is associated with a route based on its file name.
**Example**: If you create `pages/about.js` that exports a React component like below, it will be accessible at `/about`.
```
function About() {
return <div>About</div>
}
export default About
```
### Pages with Dynamic Routes
Next.js supports pages with dynamic routes. For example, if you create a file called `pages/posts/[id].js`, then it will be accessible at `posts/1`, `posts/2`, etc.
> To learn more about dynamic routing, check the [Dynamic Routing documentation](/docs/routing/dynamic-routes.md).
## Pre-rendering
By default, Next.js **pre-renders** every page. This means that Next.js generates HTML for each page in advance, instead of having it all done by client-side JavaScript. Pre-rendering can result in better performance and SEO.
Each generated HTML is associated with minimal JavaScript code necessary for that page. When a page is loaded by the browser, its JavaScript code runs and makes the page fully interactive. (This process is called _hydration_.)
### Two forms of Pre-rendering
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.
- [**Static Generation (Recommended)**](#static-generation-recommended): The HTML is generated at **build time** and will be reused on each request.
- [**Server-side Rendering**](#server-side-rendering): The HTML is generated on **each request**.
Importantly, Next.js lets you **choose** which pre-rendering form you'd like 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.
We **recommend** using **Static Generation** over Server-side Rendering for performance reasons. Statically generated pages can be cached by CDN with no extra configuration to boost performance. However, in some cases, Server-side Rendering might be the only option.
You can also use **Client-side Rendering** along with Static Generation or Server-side Rendering. That means some parts of a page can be rendered entirely by client side JavaScript. To learn more, take a look at the [Data Fetching](/docs/basic-features/data-fetching.md#fetching-data-on-the-client-side) documentation.
## Static Generation (Recommended)
If a page uses **Static Generation**, the page HTML is generated at **build time**. That means in production, the page HTML is generated when you run `next build` . This HTML will then be reused on each request. It can be cached by a CDN.
In Next.js, you can statically generate pages **with or without data**. Let's take a look at each case.
### Static Generation without data
By default, Next.js pre-renders pages using Static Generation without fetching data. Here's an example:
```
function About() {
return <div>About</div>
}
export default About
```
Note that this page does not need to fetch any external data to be pre-rendered. In cases like this, Next.js generates a single HTML file per page during build time.
### Static Generation with data
Some pages require fetching external data for pre-rendering. There are two scenarios, and one or both might apply. In each case, you can use a special function Next.js provides:
1. Your page **content** depends on external data: Use `getStaticProps`.
2. Your page **paths** depend on external data: Use `getStaticPaths` (usually in addition to `getStaticProps`).
#### Scenario 1: Your page **content** depends on external data
**Example**: Your blog page might need to fetch the list of blog posts from a CMS (content management system).
```
// TODO: Need to fetch `posts` (by calling some API endpoint)
// before this page can be pre-rendered.
function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li>{post.title}</li>
))}
</ul>
)
}
export default Blog
```
To fetch this data on pre-render, Next.js allows you to `export` an `async` function called `getStaticProps` from the same file. This function gets called at build time and lets you pass fetched data to the page's `props` on pre-render.
```
function Blog({ posts }) {
// Render posts...
}
// This function gets called at build time
export async function getStaticProps() {
// Call an external API endpoint to get posts
const res = await fetch('https://.../posts')
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts
}
}
}
export default Blog
```
To learn more about how `getStaticProps` works, check out the [Data Fetching documentation](/docs/basic-features/data-fetching.md#getstaticprops-static-generation).
#### Scenario 2: Your page paths depend on external data
Next.js allows you to create pages with **dynamic routes**. For example, you can create a file called `pages/posts/[id].js` to show a single blog post based on `id`. This will allow you to show a blog post with `id: 1` when you access `posts/1`.
> To learn more about dynamic routing, check the [Dynamic Routing documentation](/docs/routing/dynamic-routes.md).
However, which `id` you want to pre-render at build time might depend on external data.
**Example**: suppose that you've only added one blog post (with `id: 1`) to the database. In this case, you'd only want to pre-render `posts/1` at build time.
Later, you might add the second post with `id: 2`. Then you'd want to pre-render `posts/2` as well.
So your page **paths** that are pre-rendered depend on external data**.** To handle this, Next.js lets you `export` an `async` function called `getStaticPaths` from a dynamic page (`pages/posts/[id].js` in this case). This function gets called at build time and lets you specify which paths you want to pre-render.
```
// This function gets called at build time
export async function getStaticPaths() {
// Call an external API endpoint to get posts
const res = await fetch('https://.../posts')
const posts = await res.json()
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id }
}))
// We'll pre-render only these paths at build time.
// { fallback: false } means other routes should 404.
return { paths, fallback: false }
}
```
Also in `pages/posts/[id].js`, you need to export `getStaticProps` so that you can fetch the data about the post with this `id` and use it to pre-render the page:
```
function Post({ post }) {
// Render post...
}
export async function getStaticPaths() {
// ...
}
// This also gets called at build time
export async function getStaticProps({ params }) {
// params contains the post `id`.
// If the route is like /posts/1, then params.id is 1
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
// Pass post data to the page via props
return { props: { post } }
}
export default Post
```
To learn more about how `getStaticPaths` works, check out the [Data Fetching documentation](/docs/basic-features/data-fetching.md#getstaticpaths-static-generation).
### When should I use Static Generation?
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.
You can use Static Generation for many types of pages, including:
- Marketing pages
- Blog posts
- E-commerce product listings
- Help and documentation
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.
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 cases like this, you can do one of the following:
- Use Static Generation with **Client-side Rendering:** You can skip pre-rendering some parts of a page and then use client-side JavaScript to populate them. To learn more about this approach, check out the [Data Fetching documentation](/docs/basic-features/data-fetching.md#fetching-data-on-the-client-side).
- Use **Server-Side Rendering:** Next.js pre-renders a page on each request. It will be slower because the page cannot be cached by a CDN, but the pre-rendered page will always be up-to-date. We'll talk about this approach below.
## Server-side Rendering
> Also referred to as "SSR" or "Dynamic Rendering".
If a page uses **Server-side Rendering**, the page HTML is generated on **each request**.
To use Server-side Rendering for a page, you need to `export` an `async` function called `getServerSideProps`. This function will be called by the server on every request.
For example, suppose that your page needs to pre-render frequently updated data (fetched from an external API). You can write `getServerSideProps` which fetches this data and passes it to `Page` like below:
```
function Page({ data }) {
// Render data...
}
// This gets called on every request
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
export default Page
```
As you can see, `getServerSideProps` is similar to `getStaticProps`, but the difference is that `getServerSideProps` is run on every request instead of on build time.
To learn more about how `getServerSideProps` works, check out our [Data Fetching documentation](/docs/basic-features/data-fetching.md#getserversideprops-server-side-rendering)
## Summary
We've discussed two forms of pre-rendering for Next.js.
- **Static Generation (Recommended):** The HTML is generated at **build time** and will be reused on each request. To make a page use Static Generation, either export the page component, or export `getStaticProps` (and `getStaticPaths` if necessary). It's great for pages that can be pre-rendered ahead of a user's request. You can also use it with Client-side Rendering to bring in additional data.
- **Server-side Rendering:** The HTML is generated on **each request**. To make a page use Server-side Rendering, export `getServerSideProps`. Because Server-side Rendering results in slower performance than Static Generation, use this only if absolutely necessary.

View file

@ -0,0 +1,13 @@
---
type: tag
title: Tagged Posts
---
import { useRouter } from 'next/router'
export const TagName = () => {
const { tag } = useRouter().query
return tag || null
}
# Posts Tagged with “<TagName/>”

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 KiB

View file

@ -0,0 +1,38 @@
const { promises: fs } = require('fs')
const path = require('path')
const RSS = require('rss')
const matter = require('gray-matter')
async function generate() {
const feed = new RSS({
title: 'Your Name',
site_url: 'https://yoursite.com',
feed_url: 'https://yoursite.com/feed.xml'
})
const posts = await fs.readdir(path.join(__dirname, '..', 'pages', 'posts'))
await Promise.all(
posts.map(async (name) => {
if (name.startsWith('index.')) return
const content = await fs.readFile(
path.join(__dirname, '..', 'pages', 'posts', name)
)
const frontmatter = matter(content)
feed.item({
title: frontmatter.data.title,
url: '/posts/' + name.replace(/\.mdx?/, ''),
date: frontmatter.data.date,
description: frontmatter.data.description,
categories: frontmatter.data.tag.split(', '),
author: frontmatter.data.author
})
})
)
await fs.writeFile('./public/feed.xml', feed.xml({ indent: true }))
}
generate()

View file

@ -0,0 +1,57 @@
@font-face {
font-family: 'Inter var';
font-style: normal;
font-weight: 100 900;
font-display: block;
src: url(/fonts/Inter-roman.latin.var.woff2) format('woff2');
}
@font-face {
font-family: 'Inter var';
font-style: italic;
font-weight: 100 900;
font-display: block;
src: url(/fonts/Inter-italic.latin.var.woff2) format('woff2');
font-named-instance: 'Italic';
}
body {
font-family: 'Inter var', system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
-webkit-font-smoothing: subpixel-antialiased;
font-feature-settings: 'case' 1, 'cpsp' 1, 'dlig' 1, 'cv01' 1, 'cv02',
'cv03' 1, 'cv04' 1;
font-variation-settings: 'wght' 450;
font-variant: common-ligatures contextual;
letter-spacing: -0.02em;
}
b,
strong,
h3,
h4,
h5,
h6 {
font-variation-settings: 'wght' 650;
}
h1 {
font-variation-settings: 'wght' 850;
}
h2 {
font-variation-settings: 'wght' 750;
}
@media screen and (min-device-pixel-ratio: 1.5),
screen and (min-resolution: 1.5dppx) {
body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
details summary {
cursor: pointer;
}
img.next-image {
margin: 0;
}

View file

@ -0,0 +1,21 @@
const YEAR = new Date().getFullYear()
export default {
footer: (
<small style={{ display: 'block', marginTop: '8rem' }}>
<time>{YEAR}</time> © Your Name.
<a href="/feed.xml">RSS</a>
<style jsx>{`
a {
float: right;
}
@media screen and (max-width: 480px) {
article {
padding-top: 2rem;
padding-bottom: 4rem;
}
}
`}</style>
</small>
)
}