16

I am trying to make a single-page application in Next.js, but I am starting to think this is not possible. Can anyone show me how?

My first attempt was to use a HashRouter:

<HashRouter>
  <div>
    <NavLink to="/">Home</NavLink>
    &nbsp;&nbsp;
    <NavLink to="/purchaseComplete">Purchase Complete</NavLink>
  </div>
  <div>
    <Route exact path="/" Component={Default} />
    <Route path="/purchaseComplete" Component={PurchaseComplete} />
  </div>
</HashRouter>

But in the browser I see "Invariant failed: Browser history needs a DOM"

I then tried to use a StaticRouter:

<StaticRouter location="/" context={staticContext}>
  <div>
    <NavLink to="/">Home</NavLink>
    &nbsp;&nbsp;
    <NavLink to="/purchaseComplete">Purchase Complete</NavLink>
  </div>
  <div>
    <Switch>
      <Route exact path="/" Component={Default} />
      <Route path="/purchaseComplete" Component={PurchaseComplete} />
    </Switch>
  </div>
</StaticRouter>

This renders the two links, but nothing happens when you click them.

In the Next.js tutorials, they talk about SPA's in the beginning, but then they only show you how to make apps with multiple pages.

I am starting to think it's not possible to build an SPA in Next.js. Can anyone confirm this? Or can someone show me how to build an SPA in Next.js?

tanguy_k
  • 11,307
  • 6
  • 54
  • 58
user2410449
  • 373
  • 1
  • 2
  • 11
  • why are you using router in nextjs? Nextjs has its own router system! create a pages directory and make a file it will be your page! single page application is a kind of concept which modern front end development made it possible. – Afsanefda Dec 28 '19 at 13:07
  • 1
    in next when you have multiple pages you still are working with a SPA! – Afsanefda Dec 28 '19 at 13:07
  • @user2410449 could you mark an answer as the accepted solution? – Colin McDonnell Dec 29 '20 at 18:25

4 Answers4

20

If you're using Next.js 9.5 or later the correct way to do this is with Rewrites. Do not use a custom server, it makes maintaining and deploying your site much harder and just isn't necessary here.

There is a detailed tutorial on how to do this here. The basic idea:

  1. Create a custom App (/pages/_app.tsx)

  2. Return null if typeof window === "undefined". This is required to prevent react-router from throwing errors during the SSR step!

// pages/_app.tsx

import { AppProps } from 'next/app';

function App({ Component, pageProps }: AppProps) {
  return (
    <div suppressHydrationWarning> // <- ADD THIS
      {typeof window === 'undefined' ? null : <Component {...pageProps} />}
    </div>
  );
}

export default App;
  1. Rewrite all routes to the homepage
// next.config.js

module.exports = {
  async rewrites() {
    return [
      // Rewrite everything else to use `pages/index`
      {
        source: '/:path*',
        destination: '/',
      },
    ];
  },
};

There is a lot more context/explanation in the linked tutorial but this will get you started.

Colin McDonnell
  • 897
  • 1
  • 11
  • 17
  • Doesn't the `typeof window === 'undefined' ? null : ` check prevent SSR and SSG? I'm trying to find a way to use this method only on specific pages that act like SPAs and use SSG elsewhere. Could the `typeof window` check be applied conditionally only on those pages? If we could get the current route in the custom app component we could perhaps do that – ronkot Feb 12 '21 at 08:57
  • 3
    @ronkot if you move that code from `_app.tsx` to `pages/index.tsx`, SSG will still work on all your other pages. The existence of a page in the `pages` directory takes precedence over the rewrite rule. From [the docs](https://nextjs.org/docs/api-reference/next.config.js/rewrites): > Rewrites are not able to override public files or routes in the pages directory as these have higher priority than rewrites. For example, if you have pages/index.js you are not able to rewrite / to another location unless you rename the pages/index.js file. – Colin McDonnell Mar 10 '21 at 02:15
  • @ColinMcDonnell but is there a way to compile as a SPA? The goal would be to deploy the SPA as an Ionic (mobile) app to be downloaded in the App Store. And have a separate deploy for just the web app and have all the fun of SSR/SSG..? – Aaron Turkel Jan 11 '22 at 18:35
  • Nope. Arguably the "pure" SPA paradigm is mutually exclusive with SSR and SSG. There's no way to both use Next's SSR/SSG capabilities and use React Router for routing. For a complex "dashboard-style" SaaS app there's really no need for it, and that's the use case where I'd recommend this approach. – Colin McDonnell Jan 12 '22 at 21:16
  • `typeof window === 'undefined'` can cause cls issues – quick007 Apr 03 '22 at 07:22
  • Does bundle splitting still work as expected when next's routing is replaced with React Router? – Qwerty Feb 13 '23 at 15:42
12

I found a blog post that answers this question: https://dev.to/toomuchdesign/next-js-react-router-2kl8

It turns out that Next.Js is intended to be used for multi-page apps.

The author explains how it is possible to use the React Router to make a single-page app with Next.JS. However, he also says the Next.JS authors responded and said they don't intend for Next.JS to be used this way!

I totally agree with the author, and am using his approach in my app. I want server-side rendering from NextJS, but also want it to be a single-page application.

user2410449
  • 373
  • 1
  • 2
  • 11
  • 5
    My whole issue with NextJS is that if I want a SPA, I have to find hacky ways to intercept stuff like manually-pasted URLs in the browser. Also, I can't use their `pages` structure, neither for my API nor my pages itself. I basically have one `index.ts` file and made it work as a SPA that way with my own Express server.. until I got to the deployment part and realized that I can't deploy to Vercel or Netflify with a custom Backend. Anyway, it's a hassle as it is, I'm considering switching back to CSR. – Mike K Aug 20 '20 at 06:20
3

I am confused by the notion that Next.js is a SPA. How can it be a SPA when you get redirected to a different page and it gets re-rendered in a browser. Instead of SPA which only refreshes part of the page.

Also when we redirect to different page, we lose all the data in the store(Redux store) in memory, as the page is refreshed and new page is rendered. So I think calling Next.js a SPA is not the right way to go. Its similar to traditional SSR apps like EJS, Jade in Express.js, but instead we do SSR separately on client side by having a next.js server for it to server side render.

Peru
  • 67
  • 3
  • 3
    Generally and if used correctly, Next.js does not reload the entire page when you go from one page to another and you would not lose the state of your Redux store. – Nick Feb 12 '21 at 16:14
2

Currently, you need to hack away NextJS to make it work as SPA , there's already a RFC that would solve all these issues layouts-rfc

Edit: layouts are now a thing on NextJS 13

Luis
  • 1,242
  • 11
  • 18