- Introduction
- TypeScript Plugin
- Special files
- Ignoring TypeScript Errors
- Custom Type Declarations
- notes
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.
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.
A project can contain both .js
/.jsx
files and .ts
/.tsx
files. In other words, TypeScript can be incrementally adopted.
The next.config.js
file must remain a JavaScript file as it does not get parsed by Babel or TypeScript.
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));
}
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>
);
}
Initially these will look the same as their jsx
equivalents.
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>
);
}
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>
);
}
These will look the same as their jsx
equivalents.
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];
}
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
});
}
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,
},
}
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"]
}
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.