Next.js single page application? - single-page-application

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>
<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>
<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?

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:
Create a custom App (/pages/_app.tsx)
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;
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.

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.

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.

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

Related

nextJS different theme for directory

I am having a small issue, I know I can use _document.js for /page however I need a different document layout for a directory. I am wondering how can I change the document theme/layout based on a sub directory.
I tried to make a new _document.js under the sub directory /staff however that did not work.
That is a good question, indeed you can only create one custom _document.js (or _document.ts if you are using typescript). To change the structure of your DOM -- adding scripts, changing <Head> balises for SEO etc...
Since _document.js is the "root" of the DOM tree, it would not make sense to be able to overwrite this file in other subfolders.
However, if you need a different Layout, I would suggest to create a custom Layout component that you can use whenever you need! In your case, you can name it StaffLayout.
Note that your problem is a common use case and Next.JS even provided a demo repository to help you achieve it.
eg.
// components/staffLayout.js
import Navbar from './navbar'
import Footer from './footer'
export default function StaffLayout({ children }) {
return (
<>
<Navbar />
<main>{children}</main>
<Footer />
</>
)
}
and then in your /staff directory
// pages/staff/backOffice.js
import StaffLayout from '../../components/staffLayout'
export default function BackOffice() {
return (
<StaffLayout>
<>enter you code here</>
</StaffLayout>
)
}
If you need multiple Layouts, Next.js created a mechanism out-of-the-box that can even remove the need for using StaffLayout every-time. It is a little bit more advance so I will only link the documentation.

how to use this.$refs in vue 3 composition api?

I use vue3-simple-html2pdf.
<vue3-simple-html2pdf ref="vue3SimpleHtml2pdf" :options="pdfOptions" :filename="exportFilename">
<div class="main-content">
<h1>PDF</h1>
</div>
</vue3-simple-html2pdf>
<button #click="download">Download pdf</button>
documentation says to do this when i want to download pdf
this.$refs.vue3SimpleHtml2pdf.download()
I use composition api and try to do like this
const vue3SimpleHtml2pdf = ref(null)
const download = () => {
vue3SimpleHtml2pdf.download()
}
but it doesn't work
how can I fix it?
Your code is not complete to understand how you have built your app. Vue App section is not shown.
UPDATE
Here is looks like you are trying to use the download() function from 'null'.
const vue3SimpleHtml2pdf = ref(null)
const download = () => {
vue3SimpleHtml2pdf.download()
}
This does not make any sense to me.
It looks like you are mixing ref() and $refs up. The first is the Special Attribute ref with access through the Vue App property $refs and the second is the ref() reactivity function. Please check the docs on how to use them.
The vue3-simple-html2pdf plugin has a great explanation page and a working sandbox app.
If you are not sure how to use the plugin with the Composition API, then I would suggest you to try to make your code work with the Options API using the sample from the Sandbox.
UPDATE 2
Please read the Docs on using the Composition API, starting from
Creating a Vue Application and especially Components Basics.
The Vue Tutorial Step 11. Components is also very helpful.
I forgot that I should refer to the
vue3SimpleHtml2pdf.value.download()
not to the
vue3SimpleHtml2pdf.download()
another solution is to call the function on click in the template
<button #click="this.$refs.vue3SimpleHtml2pdf.download()">Download pdf</button>

Gatsby wrapRootElement/wrapPageElement works on localhost, but not once deployed to shared hosting

I'm using Gatsby for the first time on a simple website project. I'm accustomed with traditional React apps where there is a root file component, typically "App.js" that one attaches Providers and other global level functionality.
Gatsby doesn't offer a root App.js, but it does offer wrapRootElement and wrapPageElement, which, after a bit of wrangling, worked just fine on my localhost.
export const wrapRootElement = ({ element }) => {
return (
<ThemeProvider theme={theme}>
{element}
</ThemeProvider>
)
}
and
export const wrapPageElement = ({ element, props }) => {
return <Layout {...props}>{element}</Layout>
}
inside of gatsby-browser.js (and with appropriate local imports and such)
(using Root for my Theme Provider and Page for my Layout wrapper, which includes header and footer elements)
I used 'gatsby clean' then 'gatsby build' to generate the deployable public folder, but upon upload to my shared host, only the inner portion showed up, not the header or footer, nor did my theme colors show up.
On a whim, I downloaded Gatsby and pulled out their "using-redux" example, built, and deployed it to the same shared host with similar results — that is, it doesn't behave as expected.
What am I missing? Since it fails on the Gatsby example, I'm presuming it as something to do with my server side setup (recall, it works fine on localhost). I have Node installed, but I'm not using it as part of this app; it's intended to be completely static and I'm just trying to use the wrappers to clean up my code.
After toying around with it some more, I replicated the code in gatsby-browser.js into gatsby-ssr.js. Voila, it worked.
This article is what inspired me, sort of: https://www.gatsbyjs.org/docs/api-files-gatsby-browser/. It states:
If you use one of them, consider if you should implement it in both
gatsby-ssr.js and gatsby-browser.js.
I had read the article earlier, but didn't take the gatsby-ssr.js file as being a requirement. Apparently, I should have interpreted the word "should" a bit more forcefully.

How to set relative paths using React Router in an existing ASP.NET Core app that is using MVC

First off, I would like to know if my course of action seems reasonable.
I am trying to integrate React within a large ASP.NET Core project. This project is a traditional MVC application. I have been tasked with adding the functionality to incorporate React for a settings page.
The page consists of 4 other settings pages. I was thinking this would be a good opportunity to incorporate client-side rendering in order to route to each settings pages.
The setup is like this.
Once the application starts, the path looks like this:
https://localhost:3000/
Navigating to settings looks like this:
https://localhost:3000/settings
I want to be able to go further and route like this:
https://localhost:3000/settings/permissions
But I don't want to hard code the route "https://localhost:3000/settings/". The problem is that the application is utilizing a traditional MVC, and the way that the user actually navigates to the settings page is totally separate from the page with react on it. All of the examples for React Router 4 seem to operate under the assumption that I have access to the props that have stored the previous location already.
So basically, I want to know if there is a way that I can get the current location regardless of where the component is being rendered, so that I can use it as the relative path, where the relative path is the base route for the router (in this case the settings page)
Here is my implementation
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route, Switch, Link } from 'react-router-dom';
import Settings from '../components/Settings';
import Permissions from '../components/Permissions';
ReactDOM.render((
<Router>
<div>
<ul>
<li>
<Link to="/">Settings</Link>
</li>
<li>
<Link to="/permissions">Permissions</Link>
</li>
</ul>
<hr />
<Route exact path="/" component={Settings} />
<Route path="/permissions" component={Permissions} />
</div>
</Router>
), document.getElementById('reactcomponentwithapidata'));
Try adding basename to your Router component with value /settings.
<Router basename="/settings">
{ /* ... */ }
</Router>
From the documentation:
The base URL for all locations. If your app is served from a sub-directory on your server, you'll want to set this to the sub-directory. A properly formatted basename should have a leading slash, but no trailing slash.

React Router - passing Slug as a prop to child Component

I am trying to create the possibility for a page to display itself so long as the data is there - I'm using WordPress REST API and the end user will be able to create pages for themselves that obviously won't appear in the Router as I can't possibly forsee all of the pages they will eventually want to create.
The way I'm trying to resolve this is to pass something of a wildcard into react-router (picture 1).
Picture 1:
So, in my understanding, if the user visited my site and put in /giraffe, it would take them to the <About /> component, and would put /giraffe in the pageSlug parameter. However, it puts :slug in that parameter.
When I use the React Dev Tools, I can see that there is an element between my <App /> (top level) component and my <About /> component with no name and has parameters - see Picture 2:
This has a component with the following information <component location={pathname: "/giraffe", search: "", hash: ""} params={slug: "giraffe"} /> etc. Is there any way that I can pass these params into the route?
Here is how I am returning my <About /> component - Picture 3:
This is the first time I've used React and React-Router, so I appreciate that this may be a stupid question or I may be doing this all wrong, however I've been trawling through questions and answers for a few hours and am none the wiser. I feel like I should be able to access those parameters, but no idea.
Help?
From the docs:
A route's component is rendered when that route matches the URL. The router will inject the following properties into your component when it's rendered.
params
The dynamic segments of the URL.
So if your route is defined as follows:
<Route path="/:slug" component={About} />
you could access this.props.params.slug to get the parameter value.

Resources