Chris.luChris.lu header image, displaying an 80s style landscape and sunset

First Typescript page

I hope you are still there because it is finally time to start coding (a bit) πŸ™‚, but first a bit of theory about routing πŸ˜‰ (feel free to skip the first chapter if you know it already), and then we create our very first page

Next.js app vs pages router (optional)

If you used the Next.js pages router in the past but did not yet use the app router, then here is a short introduction (as in this tutorial we will exclusively use the app router)

With the pages router, if you wanted to have a page at www.example.com/foo, then you would create a file named foo.tsx inside of the pages folder

With the app router if you want a page at that same www.example.com/foo path, then you create a folder named foo inside of the app folder (and add a page.tsx file inside of it)

Let's assume the path is now www.example.com/foo/bar, when using the pages router foo would be a folder inside of pages, but bar would be a file, however, when using the app router, every segment is a folder

The other difference are the page files, with the pages router, the name of the file gets used as the last segment of a URL, in the app router, every page is always named page(.jsx|.tsx) and all directories will be a segment of the URL

The advantage of using a folder as the last segment and having a convention that says that every page needs to be named page(.jsx|.tsx) is that with the introduction of the app router Next.js added a bunch of other file conventions, for example you can have a layout in every segment folder, you can have a not-found file in every folder and so on, meaning you can create different layouts for different parts of the website easily, or even create different error, loading, not-found, ... pages for various parts of your project (without having to add logic inside those files to check what the current path is and then show a different layout, UI or content based on what the path is)

A feature you will already know if you used the pages router is dynamic routes. To create a dynamic route segment, you wrap the folder's name in square brackets, for example, with a folder structure like this /app/articles/[slug], the slug could be anything, so if the URL is www.example.com/articles/foo then the slug have "foo" as value and for another URL www.example.com/articles/bar the slug would be a "bar", to retrieve the slug value you would create a page with the following code:

/app/articles/[slug]/page.tsx
export default async function Page({
    params,
}: {
    params: Promise<{ slug: string }>
}) {
    const slug = (await params).slug
    return <div>My article slug is: {slug}</div>
}

Side note: dynamic pages now need to be async functions, due to params being promises starting with Next.js 15 (Async Request APIs, Next.js 15 breaking change)

Other useful features were introduced with the new router, like route groups which we will see in a future part of this tutorial. Another new feature are parallel routes but those I will not cover them in this tutorial, but it is good to know they exist as they might become useful sooner or later as your project grows

If you haven't read my The road to React 19 and Next.js 15 post yet but want to know more about the Next.js pages VS app router, then you might want to check out the "pages VS app router" chapter

Our 1st typescript page

Start by opening the app folder, which create-next-app created for us during the initial setup of our project

If you have a bit of time, have a look at what Next.js 15 has put in there (it's always good to have a look at what the Next.js team recommends), but after that, delete all the files in the /app folder as I want to go step by step through the process of creating a Next.js blog, you can also delete the content in the /public folder as we won't need the assets of the demo project anymore

Next, create a new file in the app folder and name it page.tsx (or page.jsx if you choose to use javascript)

Then add the following content into the page.tsx file and finally save it

/app/page.tsx
export default function Home() {
 
    return (
        <>
            <h1>Hello World?</h1>
        </>
    )
 
}

Congratulations πŸŽ‰ you just coded your first Next.js page in typescript, next we will make sure the page actually works

Start the development server

Now open the VSCode terminal if it isn't open yet (or use your favorite command line tool), and let's use one of the 4 commands create-next-app added to the package.json scripts (and which we documented in README.md earlier) to start the development server:

npm run dev

Now, in the terminal, press Ctrl and then click on the Next.js local server URL or open your browser and put the following URL into the address bar: http://localhost:3000/

As you can see, Next.js has compiled our typescript page, and the development server has responded to the browser request, which is why we can see our "Hello World?" message

The root layout is required

Go back to VSCode and look at the list of files in the sidebar

You will notice that Next.js re-added the /app/layout.tsx file (when we started the dev server) we just deleted earlier. This is because this layout file is called the root layout and it is required (and because Next.js is a clever framework that in many places helps you do the right thing πŸ˜‰), also if you look at your VSCode terminal you will see that Next.js printed the following line, informing us that it created the layout file for us:

⚠ Your page app/page.tsx did not have a root layout. We created app\layout.tsx for you.

No root layout replacement for you Turbo users

We just saw that when using the npm run dev command, Next.js will automatically create a root layout file, this is however only the case when you have removed the --turbopack flag from the dev script command (in our package.json) as we did in a previous Typescript plugin and typed routes chapter

If you use the npm run dev-turbo command (or if you prefer, use npm run dev -- --turbopack, note that use two hyphen (--) before the --turbopack flag, this is because we do NOT want to apply the flag to our dev script but instead want to pass the flag the to the next dev command inside of our dev script) and you do NOT have root Layout, then Next.js will NOT automatically create one for you πŸ˜”.

After starting the development server with turbopack, if you then visit your localhost (http://localhost:3000/) in the browser, instead of getting a root layout file you will see the following error message:

Missing required html tags
The following tags are missing in the Root Layout: <html>, <body>.
Read more at https://nextjs.org/docs/messages/missing-root-layout-tags

The fix for Turbo users is to create the layout file you will see in the next chapter manually 😏.

Root layout content

If you open the /app/layout.tsx file, you will see that on top, there is the following code:

/app/layout.tsx (part 1)
export const metadata = {
    title: 'Next.js',
    description: 'Generated by Next.js',
}

As you can see, Next.js has added a metadata object, this is the Next.js metadata API that we will soon use in layouts and pages to set the tags in the <head> element, like the title and description (I will go more in detail in a future chapter) but also open graph tags

The second part it has added is just a basic Next.js RootLayout function that adds basic HTML elements and the children prop that will contain the content of the pages that get wrapped by the layout:

/app/layout.tsx (part 2)
export default function RootLayout({
    children,
}: {
    children: React.ReactNode
}) {
    return (
        <html lang="en">
            <body>{children}</body>
        </html>
    )
}

This layout is still very basic, it only contains the bare minimum of HTML elements to create a valid HTML document, but in future chapters we will add more code to make this layout file more useful

Edit the first page

As you might have noticed, I added a question mark in the Hello World? heading text

Let's replace the question mark with an exclamation mark and then save the file:

/app/page.tsx
export default function Home() {
 
    return (
        <>
            <h1>Hello World!</h1>
        </>
    )
 
}
Note

As soon as you save the file, you will see in the terminal that Next.js prints a message Compiled in Xms (Y modules), which shows you that Next.js detected changes in your code base and did a new build for you

Now go back into your browser to have another look at the rendered page using the https://localhost:3000/ URL

Even though you haven't reloaded the page manually, you will notice that your changes have been applied, which is because Next.js has a feature called fast refresh, this feature will watch your code base and each time a page gets rebuilt it will also refresh the page in the browser

Next.js Hot Module Reload (HMR)

Let's go back to our project, make sure the dev server is running or use the npm run dev command to start it and then open http://localhost:3000/ in your browser

In your browser right click somewhere on the page and then select Inspect (or open the browser dev tools by pressing the F12 and then open the Elements tab), you will see that Next.js injects a bunch of Javascript code into our page and some of those javascript files are heavy, this is because Next.js adds, for example, a tool called Hot Module Reload (HMR) (all the HMR code won't get loaded in production, Next.js only adds those files to our page when in development mode)

HMR starts watching for file changes as soon as you start the development server

If we edit and save (or add a new file), HMR will detect the change and tell Next.js to (re-)compile the files, then Next.js fast refresh will update the output in the browser for us

Stop the development server

We started the development server earlier, but how do we stop it? If you have never done it before, it might not be obvious how to do it

The easiest way to stop the development is to press Ctrl+S (macOS: ⌘S, Linux: Ctrl+S)

Then you will get asked if you want to quit:

Terminate batch job (Y/N)?

To confirm, either enter Y and then press ENTER or just press Ctrl+S (macOS: ⌘S, Linux: Ctrl+S) again