From 0d5a692d0e85293e2997ed05c9914e5b169f0f64 Mon Sep 17 00:00:00 2001 From: Shawn Callegari <36091529+shawncal@users.noreply.github.com> Date: Sun, 7 Aug 2022 09:44:18 -0700 Subject: [PATCH] Port 'with-static-export' example to TypeScript (#38268) The 'with-static-export' example is very useful but, if a developer wants to use TypeScript, converting it is nontrivial. There are some gotchas in the GetStaticProps/GetStaticPaths functions. This example should help jumpstart TS development for sites static generation. ## Documentation / Examples - [x] Make sure the linting passes by running `pnpm lint` - [x] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples) Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com> --- .../components/{post.js => post.tsx} | 3 +- .../with-static-export/lib/postdata_api.ts | 17 ++++++ examples/with-static-export/next-env.d.ts | 5 ++ examples/with-static-export/package.json | 23 +++++--- examples/with-static-export/pages/index.js | 34 ----------- examples/with-static-export/pages/index.tsx | 37 ++++++++++++ .../with-static-export/pages/post/[id].js | 48 ---------------- .../with-static-export/pages/post/[id].tsx | 57 +++++++++++++++++++ examples/with-static-export/tsconfig.json | 20 +++++++ examples/with-static-export/types/postdata.ts | 14 +++++ 10 files changed, 168 insertions(+), 90 deletions(-) rename examples/with-static-export/components/{post.js => post.tsx} (64%) create mode 100644 examples/with-static-export/lib/postdata_api.ts create mode 100644 examples/with-static-export/next-env.d.ts delete mode 100644 examples/with-static-export/pages/index.js create mode 100644 examples/with-static-export/pages/index.tsx delete mode 100644 examples/with-static-export/pages/post/[id].js create mode 100644 examples/with-static-export/pages/post/[id].tsx create mode 100644 examples/with-static-export/tsconfig.json create mode 100644 examples/with-static-export/types/postdata.ts diff --git a/examples/with-static-export/components/post.js b/examples/with-static-export/components/post.tsx similarity index 64% rename from examples/with-static-export/components/post.js rename to examples/with-static-export/components/post.tsx index 42594ee959e..15acdaea47f 100644 --- a/examples/with-static-export/components/post.js +++ b/examples/with-static-export/components/post.tsx @@ -1,6 +1,7 @@ import Link from 'next/link' +import { PostData } from '../types/postdata' -export default function Post({ title, body, id }) { +export default function Post({ title, body, id }: PostData) { return (

{title}

diff --git a/examples/with-static-export/lib/postdata_api.ts b/examples/with-static-export/lib/postdata_api.ts new file mode 100644 index 00000000000..9972126978d --- /dev/null +++ b/examples/with-static-export/lib/postdata_api.ts @@ -0,0 +1,17 @@ +import { PostData } from '../types/postdata' + +export async function GetPost(id: string): Promise { + const response = await fetch( + `https://jsonplaceholder.typicode.com/posts/${id}` + ) + const postData: PostData = (await response.json()) as PostData + return postData +} + +export async function GetPosts(): Promise { + const response = await fetch( + 'https://jsonplaceholder.typicode.com/posts?_page=1' + ) + const postList: PostData[] = (await response.json()) as PostData[] + return postList +} diff --git a/examples/with-static-export/next-env.d.ts b/examples/with-static-export/next-env.d.ts new file mode 100644 index 00000000000..4f11a03dc6c --- /dev/null +++ b/examples/with-static-export/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/examples/with-static-export/package.json b/examples/with-static-export/package.json index 4077bfc050c..75149ac2aa3 100644 --- a/examples/with-static-export/package.json +++ b/examples/with-static-export/package.json @@ -1,17 +1,26 @@ { "private": true, - "dependencies": { - "next": "latest", - "react": "^17.0.2", - "react-dom": "^17.0.2", - "serve": "11.2.0" - }, "scripts": { "dev": "next", "build": "next build", "preexport": "npm run build", "export": "next export", "prestart": "npm run export", - "start": "serve out" + "start": "serve out", + "lint": "next lint" + }, + "dependencies": { + "next": "latest", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "serve": "^14.0.1" + }, + "devDependencies": { + "@types/node": "^18.0.0", + "@types/react": "^18.0.14", + "@types/react-dom": "^18.0.5", + "eslint": "8.19.0", + "eslint-config-next": "12.2.0", + "typescript": "^4.7.4" } } diff --git a/examples/with-static-export/pages/index.js b/examples/with-static-export/pages/index.js deleted file mode 100644 index 535bf7f526a..00000000000 --- a/examples/with-static-export/pages/index.js +++ /dev/null @@ -1,34 +0,0 @@ -import Head from 'next/head' - -import Post from '../components/post' - -export async function getStaticProps() { - // fetch list of posts - const response = await fetch( - 'https://jsonplaceholder.typicode.com/posts?_page=1' - ) - const postList = await response.json() - return { - props: { - postList, - }, - } -} - -export default function IndexPage({ postList }) { - return ( -
- - Home page - - -

List of posts

- -
- {postList.map((post) => ( - - ))} -
-
- ) -} diff --git a/examples/with-static-export/pages/index.tsx b/examples/with-static-export/pages/index.tsx new file mode 100644 index 00000000000..499d91e3789 --- /dev/null +++ b/examples/with-static-export/pages/index.tsx @@ -0,0 +1,37 @@ +import Head from 'next/head' +import { GetStaticProps, NextPage } from 'next' +import Post from '../components/post' +import { PostData, PostDataListProps } from '../types/postdata' +import { GetPosts } from '../lib/postdata_api' + +export const getStaticProps: GetStaticProps = async (_context) => { + // fetch list of posts + const posts: PostData[] = await GetPosts() + return { + props: { + postDataList: posts, + }, + } +} + +const IndexPage: NextPage = ({ + postDataList, +}: PostDataListProps) => { + return ( +
+ + Home page + + +

List of posts

+ +
+ {postDataList.map((post: PostData) => ( + + ))} +
+
+ ) +} + +export default IndexPage diff --git a/examples/with-static-export/pages/post/[id].js b/examples/with-static-export/pages/post/[id].js deleted file mode 100644 index d3f695916d2..00000000000 --- a/examples/with-static-export/pages/post/[id].js +++ /dev/null @@ -1,48 +0,0 @@ -import Link from 'next/link' -import Head from 'next/head' - -export async function getStaticPaths() { - const response = await fetch( - 'https://jsonplaceholder.typicode.com/posts?_page=1' - ) - const postList = await response.json() - return { - paths: postList.map((post) => { - return { - params: { - id: `${post.id}`, - }, - } - }), - fallback: false, - } -} - -export async function getStaticProps({ params }) { - // fetch single post detail - const response = await fetch( - `https://jsonplaceholder.typicode.com/posts/${params.id}` - ) - const post = await response.json() - return { - props: post, - } -} - -export default function Post({ title, body }) { - return ( -
- - {title} - - -

{title}

- -

{body}

- - - Go back to home - -
- ) -} diff --git a/examples/with-static-export/pages/post/[id].tsx b/examples/with-static-export/pages/post/[id].tsx new file mode 100644 index 00000000000..58787b5f6e0 --- /dev/null +++ b/examples/with-static-export/pages/post/[id].tsx @@ -0,0 +1,57 @@ +import Link from 'next/link' +import Head from 'next/head' +import React from 'react' +import { GetStaticProps, GetStaticPaths, NextPage } from 'next' +import { ParsedUrlQuery } from 'querystring' +import { PostData, PostDataProps } from '../../types/postdata' +import { GetPosts, GetPost } from '../../lib/postdata_api' + +interface Params extends ParsedUrlQuery { + id: string +} + +export const getStaticPaths: GetStaticPaths = async () => { + const postList: PostData[] = await GetPosts() + return { + paths: postList.map((post) => { + return { + params: { + id: post.id.toString(), + }, + } + }), + fallback: false, + } +} + +export const getStaticProps: GetStaticProps = async ( + context +) => { + const { id } = context.params! as Params + const postData: PostData = await GetPost(id) + return { + props: { + postData, + }, + } +} + +const Post: NextPage = ({ postData }: PostDataProps) => { + return ( +
+ + {postData.title} + + +

{postData.title}

+ +

{postData.body}

+ + + Go back to home + +
+ ) +} + +export default Post diff --git a/examples/with-static-export/tsconfig.json b/examples/with-static-export/tsconfig.json new file mode 100644 index 00000000000..77c7604081d --- /dev/null +++ b/examples/with-static-export/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "incremental": true, + "esModuleInterop": true, + "module": "esnext", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "moduleResolution": "node" + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/examples/with-static-export/types/postdata.ts b/examples/with-static-export/types/postdata.ts new file mode 100644 index 00000000000..7540f93fc3a --- /dev/null +++ b/examples/with-static-export/types/postdata.ts @@ -0,0 +1,14 @@ +export interface PostData { + userId: number + id: number + title: string + body: string +} + +export interface PostDataProps { + postData: PostData +} + +export interface PostDataListProps { + postDataList: PostData[] +}