Skip to content

Deploying with Custom Serverless Next.js Routing

Next.js offers developers a built-in routing system that allows you to link to other pages whilst also giving those pages a custom URL. When a user refreshes the custom URL, however, the browser will not be able to load the link because the page was previously routed client-side. To resolve this, we need to also handle routing server-side.

Warning: This guide requires Next.js 9+ to work correctly.

When you link to a page within the same Next.js app, you can provide two options. The first and required option is href. This is a link to the page as it exists in the pages directory. The second is as which describes the URL Next.js should show when routing to that page.

For example, if you have a page located at pages/product.js in your codebase, you can use the href option with the value /product?name=<product_name> to route and render that page with a query parameter, however, if you want to show the URL /product/<product_name>, you can use the as option to format the URL when routing.

This is an amazing feature of Next.js, however, it cannot control how the server routes to the app if the routes are not defined with the server.

The solution to this problem was previously using a custom server. For example, you could create an Express app, catch the request to /product/<product_name> and route it to the /product?name=<product_name> file behind the scenes so that you can keep the custom URL:

server.get('/products/:name', (req, res) => {
  return app.render(req, res, '/product', { name: });

An example custom server route capture and render method.

Warning: The custom server method will not work with the Vercel platform.

With Vercel and the serverless approach, each Next.js page is a separate entry point, making the sole custom server entry point a thing of the past for the good of performance and stability. Fortunately, Vercel offers a solution, handling the configuration of routes for you.

By making use of Next.js path segments with Vercel, dynamic routing is achieved through the file structure. No longer are custom servers or additional configuration required, simplifying the process of building your app significantly.

If you have yet to set up routing for your Next.js app, this brief example describes how to use Next.js routing with Vercel to support custom URLs.

The first part of creating defined routes is to make sure those routes can be rendered as pages. In this example, we will have two pages, "index" (our homepage) and "product".

The index.js inside of our pages directory is just a small page that exports a heading and a paragraph of text with a link to our future products page using a Next.js Link component:

import Link from 'next/link';

export default () => (
    <h1>Welcome to our Next.js website!</h1>
      View our{' '}
      <Link href="/product/espresso">
        <a>espresso product</a>

An example index.js file for our project.

Note: In a real app, the product name query parameter would most likely be dynamic based on other factors, but for simplicity, we'll use "espresso" as the product name.

Next, for our link to work, we need to create a /product directory inside of /pages, then a [name].js file inside of /product. This page displays the query parameter, which we receive with the useRouter() hook, as the title and a paragraph:

import { useRouter } from 'next/router';

const Product = () => {
  const router = useRouter();
  const { name } = router.query;

  return (
    // `name` is defined after hydrating client-side
    name && (
        <p>Welcome to our product page for {name}!</p>

export default Product;

An example [name].js file for our project.

Now, clicking the espresso product link will result in the URL path being /product/espresso while rendering the product page with the name query parameter set to espresso.

To deploy your Next.js app with Vercel for Git, make sure it has been pushed to a Git repository.

Import the project into Vercel using your Git provider of choice:

After your project has been imported, all subsequent pushes to branches will generate Preview Deployments, and all changes made to the Production Branch (commonly "main") will result in a Production Deployment.

Couldn't find the guide you need?