on NextJS How to prerender shared components of my site? - javascript

I have a site built with nextjs with a top navigation bar and a footer. Since those are the same on every page, I didn't add them to their individual js files under pages/, instead, I have a 'wrapper' that adds those to each page. The data of both come from a JSON (2 different languages).
I was trying to use getServerSideProps but that won't work because the navigation bar itself is not inside the page.
What is the 'correct' way of doing this? I cannot hardcode the JSON in the code because it might change and I would prefer not to have to add those sections on the code of every page I have.
I see that the nextjs is preparing a release with the possibility to get static props in _app.js but what is the current solution for this problem?
_app.js
export default function MyApp({ Component, pageProps }) {
return (
<AppWrapper>
<Component {...pageProps} />
</AppWrapper>
);
}
in AppWrapper I create the app context with react hook (createContext) and then wrap it
const WebWrapper = ({ children }) => {
return (
<>
<NavBar />
<main>{children}</main>
<Footer />
</>
);
};

Related

Next.js 13 (app dir) - Access page metadata in Layout

I am using the new App folder structure with Next.js 13 project, and I would like to display a subheader in the RootLayout containing the value of the Page title (I'm using metadata on each page to configure the head title).
Is there any way to directly access that value with SSR?
Example layout.js:
export default function RootLayout({ children) {
const pageTitle = "???"; // How to get this value?
return (
<html>
<body>
<HeaderComponent />
<div className="subheader">
<h3>{pageTitle}</h3> <------ HERE
</div>
<div className="content">{children}</div>
<FooterComponent />
</body>
</html>
)
);
Example page.js:
export const metadata = {
title: "Login"
};
export default function Login() {
return (
<div>Login form</div>
);
}
No, you cannot get that metadata in the layout with SSR, at least so far. A layout in the app folder gets passed an object that contains children and params when a dynamic route is used. That's it.
Its role is to wrap all components of a route segment, not only page.js, but also loading.js, not-found.js...
Anything specific to a component should be in it directly. A page title should be in page.js itself. Next.js decided that a project should be structured so.

What is the best way to get shopOrigin from within Shopify react app?

Overview:
I am using Shopify's CLI to generate an embedded React app. I would like to be able to get the shopOrigin (see code below) from other pages within the application.
Problem:
I have attempted using store-js to store the shopOrigin. This works at first but it seems to clear when I navigate to any other pages within the application.
Note:
Since it is set properly in _app.js I feel that I should simply be able to get the shopOrigin further downstream (i.e. in my index page or any other page) without having to set it in storage.
So, I am completely open to obtaining the shopOrigin a different way without storing it via store-js or any other storage mechanism.
Nonetheless, here is my current code.
Code:
// _app.js
import store from "store-js";
// other imports
class MyApp extends App {
render() {
const { Component, pageProps, shopOrigin } = this.props;
store.set("shopUrl", shopOrigin);
return (
<AppProvider i18n={translations}>
<Provider
config={{
apiKey: API_KEY,
shopOrigin: shopOrigin,
forceRedirect: true,
}}
>
<ClientRouter />
<MyProvider Component={Component} {...pageProps} />
</Provider>
</AppProvider>
);
}
}
// index.js
import store from "store-js";
// other imports
function Index(props) {
console.log("SHOP URL IS HERE: " + store.get("shopUrl"));
}

How do I conditionally render a component in Gatsby based on specific page?

I'm building a website on Gatsby and I will like to be able to render a component conditionally based on which page I am. For example, in the footer I have a component that I don't want it to appear on the "ourcontent" page but yes for the rest of the site.
I tried doing this, which worked, however after running build gives me an error saying:
"window" is not available during server side rendering.
{ window.location.pathname !== '/ourcontent'
? <MyComponent />
: null
}
Im working using a Functional Component.
How would I be able to achieve this? Any help will be extremely appreciated! Thank you very much in advanced!
You can also use #reach/router. Here is an ultra simple example.
import { Location } from '#reach/router'
const IndexPage = () => (
<Layout>
<h1>Hi people</h1>
<Location>
{({ location }) => {
console.log(location)
if (location === '/') return <p>You are on the homepage {location.pathname}</p>
return <h1> you are not on the homepage </h1>
}}
</Location>
</Layout>
)
export default IndexPage
This codesandbox shows both the #reach/router and the location.pathname implmentations.
On any Gatsby page component you should have a location prop. Then you can get the pathname using props.location.pathname. Pretty sure this should work on server-side-rendering (your build) as well.
The simplest and easiest (the cleanest) way to achieve it is to use the built-in props provided by Gatsby. As you can see in their documentation, it exposes a location prop which contains all the information needed. Of course, because it's not a global browser object, such as window or document, it will be available during the server-side rendering as well as during the runtime.
You can simply:
import React from "react"
import { graphql } from "gatsby"
const Component = ({ location, data }) => {
return location.pathname !== 'ourcontent' ? <MyComponent /> : null
}
export default Component
You can improve it by removing the ternary condition with location.pathname !== 'ourcontent' && <MyComponent />, is not needed to return a null.

React Suspense lazy loading without fallback

I want to lazy load my components to decrease my initial bundle size and get components on the fly using code splitting using react router.
However, when using React Suspense, they force you to use a fallback for loading.
This wouldn't work:
const lazyLoadComponent = Component =>
props => (
<Suspense> // Missing fallback property
<Component {...props} />
</Suspense>
);
In my case I am rendering html from the server so I don't want to use a spinner.
This would create a useless flicker on my screen!
I.e.:
Html loads
Place holder appears
PageComponent for the route gets loaded
I have my own spinner that loads a feed from within the page component
In my case the html corresponds to the react component that gets loaded.
Is there any known hack to easily work around this problem (except for creating a loader for any route that copies the html (!!), which by the way, would make lazy loading useless).
I am a bit displeased with "forcing" us to add a loader and I don't understand the logic behind the decision to make it mandatory.
Try to use code splitting in the docs
fallback props is just a React element, you can set it for null.
const MyLazyComponent= React.lazy(() => import('./MyComponent'));
<Suspense fallback={null}>
<MyLazyComponent />
</Suspense>
I created an issue for this on Github: https://github.com/facebook/react/issues/19715
There isn't a current clean solution using React-Router / React.
This is however foreseen in a future release using concurrent mode. As mentioned by Dan Abramov:
Regarding your concrete feature request, I think I can reframe it
slightly differently. It's not that you want "optional fallback" since
that wouldn't make sense for new screens (we've got to show
something). What I believe you're looking for is a way to skip showing
the fallback if the content is already in HTML. This is precisely how
React behaves in Concurrent Mode so the feature request is already
implemented (and will eventually become the default behavior in a
stable release).
For me it is not a problem to wait, so currently I will omit lazy-loading the routes as this concerns a hobby-project and I have time to wait for a future release.
In my experience (with React 17), there's no flickering happens if you pass null to fallback param.
I have a Modal component that renders lazy components.
Here's my Typescript solution:
type LazyLoadHOC = {
component: React.LazyExoticComponent<any>,
fallback?: React.ComponentType | null,
[x:string]: any
};
export const LazyLoad: React.FC<LazyLoadHOC> = ({
component: Component, fallback = null, ...props
}) => {
return (
<React.Suspense fallback={fallback}>
<Component {...props} />
</React.Suspense>
);
};
Here's my Modal:
const AddressFormModel = React.lazy(() => import('#components/address/address-form-modal'));
<Modal show={isOpen} backdrop={'static'} dialogClassName='custom-modal'>
<ModalBody>
{view === 'ADDRESS-FORM' && <LazyLoad component={AddressFormModel} />}
</ModalBody>
</Modal>
This will ensure to not trigger your global React.Suspense.
So basically all we have to do is, wrap the loader in a component and load the script using that component (Loadable here).
This way we can use React.lazy wihtout any animation.
Since our fallback is empty you wont see any animation and your content will be visible always. There will not be any flicker of content hiding.
loadable.js
import React, { Suspense } from 'react';
const Loadable = (Component) => (props) => (
<Suspense fallback={<></>}>
<Component {...props} />
</Suspense>
);
export default Loadable;
We can wrap the import in Loadable so that it will load lazy.
route.js
import React, { lazy } from 'react';
import Loadable from './components/Loadable';
// page import
const Home = Loadable(lazy(() => import('./pages/home')));
// define routes
const routes = [
{
path: "dashboard",
element: <Layout />,
children: [
{
path: "",
element: <Home />,
},
]
},
]

How to use page query while using root-wrapper for providing Theme with Gatsby

I am using wrapRootElement function in gatsby for providing ThemeProvider of styled-component on the root element. Here is the code :
export const wrapRootElement = () => {
if (typeof window === 'undefined') return <span>Something went wrong!</span>
return(
<ThemeProvider theme={theme}>
<FadeTransitionRouter>
<Page path="/" page={<IndexPage />} />
<Page path="/plan" page={<PlanPage />} />
<Page path="/promos/:slug" page={<Template />} />
</FadeTransitionRouter>
</ThemeProvider>
)};
Because of this I am compelled to use staticQuery in pages as page queries are not working there. But I need to use page queries to introduce variables in graphql queries. How can I achieve this ? If I do not use wrapRootElement, my page queries works fine. But it is important for me to use it as I need to provide my external Theme to all the pages.
Thanks in advance.
You aren't wrapping the root element here, you're replacing the entirety of the Gatsby app tree. You're probably seeing warnings about page queries being ignored because they're in files that aren't being treated like pages, which is likely where the source of confusion is.
Here's the example the Gatsby docs provide:
exports.wrapRootElement = ({ element }) => {
return (
<Provider store={store}>
{element}
</Provider>
)
}
Note how this example accepts an element prop that it passes down as the children to the Provider component? That's what you're missing. You probably want something like this:
export const wrapRootElement = ({ element }) =>
<ThemeProvider theme={theme}>
{element}
</ThemeProvider>
You don't need to check for the existence of window here. If you're in gatsby-browser.js it will only ever be executed in the context of the browser. If you're in gatsby-ssr.js it'll only ever be executed in the context of node. Either way, they need to produce the same DOM output in order to have React hydrate cleanly. Bailing in the way you've shown will result in your entire DOM being replaced when React hydrates, which is less efficient than a client-side rendered app.
Finally, I removed the FadeTransitionRouter bit because you haven't shared what that is and it is unlikely to be compatible with Gatsby routing. If you'd like to have page transitions, this page in the documentation has details on how to set this up.

Categories