Skip to content

Latest commit

 

History

History
307 lines (237 loc) · 7.55 KB

nextjs_and_typescript.md

File metadata and controls

307 lines (237 loc) · 7.55 KB

Next.js + TypeScript

Table of contents

Introduction

npx create-next-app@latest will automatically install the necessary packages and configuring settings for TypeScript:

  • typescript
  • @types/node
  • @types/react
  • @types/react-dom

You will also have a tsconfig.json instead of a jsconfig.json.

See react_and_typescript.md for React specific TypeScript notes.

TypeScript Plugin

Next.js recommends that you use their TypeScript Plugin for your editor. In order to use it, your project has to be open in its own workspace (at the root) in vscode.

In the command palette (⇧⌘P) search for TypeScript: Select TypeScript Version and choose Use Workspace Version as opposed to the vscode version. Using the workspace version form node_modules gives you more type checking, hover information and lists of options for Next.js things like route segment config options:

export const dynamicParams = false;
export const revalidate = 300;
export const dynamic = 'auto';

It's recommended to use the Next.js one as they are constantly adding support for things like their special files.

Special files

A project can contain both .js/.jsx files and .ts/.tsx files. In other words, TypeScript can be incrementally adopted.

next.config.js

The next.config.js file must remain a JavaScript file as it does not get parsed by Babel or TypeScript.

middleware.ts

Import the NextRequest type and use that for the middleware request param:

import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export const config = {
  matcher: '/example/:path*',
};

export function middleware(request: NextRequest) {
  console.log('Middleware running');
  return NextResponse.redirect(new URL('/example', request.url));
}

app/layout.tsx

Import the Metadata type and use that for the metadata object. Pass { children }: Readonly<{ children: React.ReactNode }> to the root layout component.

import type { Metadata } from 'next';
import './globals.css';

// Meta data
export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app'
};

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="en">
      <body>
        {children}
      </body>
    </html>
  );
}

app/page.tsx

Initially these will look the same as their jsx equivalents.

app/error.tsx

The {error, reset} params will need to be typed:

'use client';

import { useEffect } from 'react';

export default function Error({
  error,
  reset
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    console.error(error);
  }, [error]);

  return (
    <div>
      <p>I am a custom error page.</p>
      <p>{error.message}</p>
      <button onClick={() => reset()}>try again</button>
    </div>
  );
}

app/global-error.tsx

Same as error.tsx:

'use client';

export default function GlobalError({
  error,
  reset
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  return (
    <html>
      <body>
        <h2>Something went wrong!</h2>
        <button onClick={() => reset()}>Try again</button>
      </body>
    </html>
  );
}

app/not-found.tsx

These will look the same as their jsx equivalents.

app/sitemap.ts

If you are not doing any async fetching to get dynamics routes, then you can use MetadataRoute.Sitemap for the return value:

import { MetadataRoute } from 'next';

export default function sitemap(): MetadataRoute.Sitemap {
  // Base url (protocol://domain/)
  const baseUrl = 'http://localhost:3000/';
  // Standard routes
  const pages = [
    '',
    'about',
    'login',
    'signup'
  ];

  // Create arrays:
  const routes = pages.map((route) => {
    return {
      url: `${baseUrl}${route}`,
      lastModified: new Date()
    }
  });

  return [...routes];
}

Otherwise, if you are using an async function here, then use Promise<MetadataRoute.Sitemap> as the return value:

import { MetadataRoute } from 'next';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  // Base url (protocol://domain/)
  const baseUrl = 'http://localhost:3000/';
  // Standard routes
  const pages = [
    '',
    'about',
    'login',
    'signup'
  ];
  // Fetch any dynamic routes
  const res = await fetch(url);
  const posts = await res.json()

  // Create arrays:
  const routes = pages.map((route) => {
    return {
      url: `${baseUrl}${route}`,
      lastModified: new Date()
    }
  });

  const dynamicRoutes = posts.map((post) => {
    return {
      url: `http://localhost:3000/post/${post.id}`,
      lastModified: new Date()
    }
  });

  return [...routes, ...dynamicRoutes];
}

app/api/route.ts

For GET routes, you can use Request for the type of the request param. Request I guess is part of the built-in types and so it doesn't need to be imported. The rest is not Next.js specific:

// A type union listing all possible api routes
type Route = '/api/demo';

// Type describing response data
type RouteDesc = [{ route: Route; description: string }];

// The response data
const routeDesc: RouteDesc = [
  {
    route: '/api/demo',
    description: 'A test api that returns a random Mr. Rogers quote.'
  }
];

export async function GET(request: Request) {
  const headers = {
    'Content-Type': 'application/json'
  };
  return new Response(JSON.stringify({ data: routeDesc }), {
    headers: headers
  });
}

Ignoring TypeScript Errors

Next.js will fail to build (next build) when TypeScript errors are present. If needed, you can disable the built-in type checking step.

In next.config.js and enable the ignoreBuildErrors option in the typescript config:

module.exports = {
  typescript: {
    // !! WARN !!
    // Allow production builds to successfully complete even if
    // your project has type errors.
    // !! WARN !!
    ignoreBuildErrors: true,
  },
}

Custom Type Declarations

You might be tempted to add your custom declarations to next-env.d.ts. However, this file is automatically generated, so any changes you make will be overwritten. Instead, you should create a new file, for example new-types.d.ts, and reference it in your tsconfig.json:

{
  "compilerOptions": {
    "skipLibCheck": true
    // ...
  },
  "include": [
    "new-types.d.ts",
    "next-env.d.ts",
    ".next/types/**/*.ts",
    "**/*.ts",
    "**/*.tsx"
  ],
  "exclude": ["node_modules"]
}

notes

When working with data coming from a database, you can manually declare the data types, but for better type-safety, Next.js recommends using Prisma, which automatically generates types based on your database schema.