Dynamically import a module Next.js, but with server side rendering - javascript

I have a [...pageId].tsx file in the /pages directory.
In getServerSideProps, the page will check a CMS, and find a list of components to render. I want to load those components serverside, then render them out, then return the page.
Using next/dynamic like below will load the components, but it will not render them serverside. They will only stream in on the client. In other words: the customer will see 'Loading...' first, then the component renders. That's not what I want, also not for SEO.
const getDynamicComponent = (c) => dynamic(() => import(`../../components/${c}`), {
loading: () => <section>Loading...</section>,
ssr: true
});
export default function SomePage({page}) {
// Load dynamic component if necessary
let DynamicComponent = getDynamicComponent(page.reactComponent);
}
return (
<DynamicComponent page={page} />
)
}
How can I achieve serverside rendering using dynamic imports?

Related

How to have the same component on different routes in next js? [duplicate]

Trying next with layout pattern:
https://github.com/zeit/next.js/tree/canary/examples/layout-component
And the problem is that Layout component get remounted on every page change. I need to use layout component as a Container so it'll fetch data from server on every mount. How can I prevent layout to get re-mounted? Or am I missing something there?
This helped me for persistent layouts. The author puts together a function that wraps your page components in your Layout component and then passes that fetch function to your _app.js. This way the _app.js is actually the components that renders the Layout but you get to specify which pages use which layout (in case you have multiple layouts).
So you have the flexibility of having multiple layouts throughout your site but those pages that share the same layout will actually share the same layout component and it will not have to be remounted on navigation.
Here is the link to the full article
Persistent Layout Patterns in Next.js
Here are the important code snippets. A page and then _app.js
// /pages/account-settings/basic-information.js
import SiteLayout from '../../components/SiteLayout'
import AccountSettingsLayout from '../../components/AccountSettingsLayout'
const AccountSettingsBasicInformation = () => (
<div>{/* ... */}</div>
)
AccountSettingsBasicInformation.getLayout = page => (
<SiteLayout>
<AccountSettingsLayout>{page}</AccountSettingsLayout>
</SiteLayout>
)
export default AccountSettingsBasicInformation
// /pages/_app.js
import React from 'react'
import App from 'next/app'
class MyApp extends App {
render() {
const { Component, pageProps, router } = this.props
const getLayout = Component.getLayout || (page => page)
return getLayout(<Component {...pageProps}></Component>)
}
}
export default MyApp
If you put your Layout component inside page component it will be re-remounted on page navigation (page switch).
You can wrap your page component with your Layout component inside _app.js, it should prevent it from re-mounting.
Something like this:
// _app.js
import Layout from '../components/Layout';
class MyApp extends App {
static async getInitialProps(appContext) {
const appProps = await App.getInitialProps(appContext);
return {
...appProps,
};
}
render() {
const { Component, pageProps } = this.props;
return (
<Layout>
<Component {...pageProps} />
<Layout />
);
}
}
export default MyApp;
Also, make sure you replace all the to <Link href=""></Link>, notice that only have change the Html tag to link.
I struggled because with this for many days, although I was doing everything else correctly, these <a> tags were the culprit that was causing the _app.js remount on page change
Even though this is the topic Layout being mounted again and again, the root cause of this problem is that you have some data loaded in some child component which is getting fetched again and again.
After some fooling around, I found none of these problem is actually what Next.Js or SWR solves. The question, back to square one, is how to streamline a single copy of data to some child component.
Context
Use context as a example.
Config.js
import { createContext } from 'react'
export default createContext({})
_App.js
import Config from '../Config'
export default function App({ Component, pageProps }) {
return (
<Config.Provider value={{ user: { name: 'John' }}}>
<Component {...pageProps} />
</Config.Provider>
)
}
Avatar.js
import { useContext } from 'react'
import Config from '../Config'
function Avatar() {
const { user } = useContext(Config)
return (
<span>
{user.name}
</span>
)
}
export default Avatar
No matter how you mount and dismount, you won't end up with re-render, as long as the _app doesn't.
Writable
The above example is only dealing with readable. If it's writable, you can try to pass a state into context. setUser will take care the set in consumer.
<Provider value={useState({})} />
const [user, setUser] = useContext(Config)
setUser is "cached" and won't be updated. So we can use this function to reset the user anytime in child consumer.
There're other ways, ex. React Recoil. But more or less you are dealing with a state management system to send a copy (either value or function) to somewhere else without touching other nodes. I'll leave this as an answer, since even we solved Layout issue, this problem won't disappear. And if we solve this problem, we don't need to deal with Layout at all.

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"));
}

Understanding imports from component pages in NextJS

I am new to the Next.JS framework and do not fully understand the logic of importing data from a component page. For example, I have created a page like example.js in my components folder, where I am running an API that pulls data to a graph. Then, inside my index.js file, where I want my graph to be displayed, it is giving me a 'Unhandled Runtime Error', because the API function is not being triggered inside of example.js.
This is how I am importing the page in /pages/index.js:
import dynamic from 'next/dynamic'
const Example = dynamic(() => import('../pages/example.js'))
//Later on inside of my return
<Example />
And from my example.js page I am exporting as follows:
function Example(props) {
return <div>
... my code
}
Then below that, I have the following inside my example.js:
export default Example
Followed by my getStaticProps function:
export async function getStaticProps() {
const res = await fetch("")
const data = await res.json()
if (!data) {
return {
notFound: true
}
}
return {
props: {
data: data.reverse(),
},
}
}
If I use this exact code on my index.js it functions properly. Any ideas as to why this doesn't run, and any solutions as to a fix?
Edit for clarity: My getStaticProps function is in my example.js file, and my issue is that it is not being triggered.
Next.js does a lot of magic to handle server-side rendering, static-site generation and code splitting, and it does it all from within the /pages folder. It's important to know that components within the /pages folder work a little differently from a regular React component. Here are some tips for you that should solve your problem.
1. Put your page components inside the /pages folder.
Components inside this folder can have a default export (the component), along with named exports (the additional Next.js-specific functions like getStaticProps). If Next.js finds the exported functions, it will run them.
2. Use next/dynamic only for dynamic imports.
Dynamic imports are lazy-loaded -- this will affect Next.js' automatic code-splitting and potentially server-side rendering. I don't know the exact inner workings, but my guess is dynamically-loading a Page component (which are special in Next) is probably what's breaking things. I tend to only use dynamic imports when I have a 3rd-party component that breaks SSR, so I need to dynamically import it so it's not server-side rendered.
The simplest way to handle this is probably:
in /pages/index.js:
import Example from '../path/to/example';
export default (props) => (
<div>
<p>My page component</p>
<Example />
</div>
);
export async function getStaticProps() {
return {
props: {
data: // ...
}
};
}
With this solution above, you can use dynamic imports as well -- what matters is that you have your Next.js-specific function exported from the file in /pages immediately:
in /pages/index.js:
import dynamic from 'next/dynamic';
const Example = dynamic(() => import('../components/example'));
export default (props) => (
<div>
<p>My page component</p>
<Example />
</div>
);
export async function getStaticProps() {
return {
props: {
data: // ...
}
};
}
Update to solve "My getStaticProps function is in my example.js file, and my issue is that it is not being triggered.":
Next.js will only call a getStaticProps function that is exported from a file in the /pages directory. If you want to define your getStaticProps method from another directory, you can do that, but you need to import it into /pages and then re-export it like so:
in /components/example.js:
export default () => (
// ... component ...
);
export function getStaticProps() {
// ... function ...
};
and in /pages/index.js:
import Example, { getStaticProps } from '../components/example';
export default Example;
export {
getStaticProps
}
This should work. However, you can't import Example using dynamic imports in this case.

GatsbyJS: How do I load a component synchronously at build time, but asynchronously at runtime?

I have a Gatsby site with a React component called ArticleBody that uses react-markdown to convert an article written in Markdown to a React tree.
As this is a bit of an expensive operation and a somewhat large component — and for SEO reasons — I'd like to pre-render ArticleBody at build time. However, I'd also like to load ArticleBody asynchronously in the client. Since the article body will already be included in the HTML, there's no rush to load and render the Markdown component in the client, so async should be fine.
How would I accomplish this? It's almost as if I want to have two different JS bundles — one bundle that loads ArticleBody synchronously, for the build, and one that loads it asynchronously, for the client. Is this possible in Gatsby?
Thanks!
Instead of React.lazy which is not supported, you can use loadable components. There is a Gatsby plugin to handle SSR correctly gatsby-plugin-loadable-components-ssr
Currently there is an issue with it since Gatsby 3.x, but there is a way to implement it yourself without the extra plugin. See the comment in the issue here. Also add the changes mentioned in the comment below of it.
I haven't tried this specific implementation yet, but it should work with the following steps:
npm install --save-dev #loadable/babel-plugin #loadable/server #loadable/webpack-plugin #loadable/component
gatsby-browser.js
import { loadableReady } from '#loadable/component'
import { hydrate } from 'react-dom'
export const replaceHydrateFunction = () => (element, container, callback) => {
loadableReady(() => {
hydrate(element, container, callback)
})
}
gatsby-node.js
exports.onCreateWebpackConfig = ({ actions, stage }) => {
if (
stage === "build-javascript" ||
stage === "develop" ||
stage === "develop-html"
) {
actions.setWebpackConfig({
plugins: [
new LoadablePlugin({
filename:
stage === "develop"
? `public/loadable-stats.json`
: "loadable-stats.json",
writeToDisk: true
})
]
});
}
};
gatsby-ssr.js
import { ChunkExtractor } from '#loadable/server'
import path from 'path'
const extractor = new ChunkExtractor({
// Read the stats file generated by webpack loadable plugin.
statsFile: path.resolve('./public/loadable-stats.json'),
entrypoints: [],
})
// extractor.collectChunks() will wrap the application in a ChunkExtractorManager
export const wrapRootElement = ({ element }) =>
extractor.collectChunks(element)
export const onRenderBody = ({ setHeadComponents, setPostBodyComponents }) => {
// Set link rel="preload" tags in the head to start the request asap. This will NOT parse the assets fetched
setHeadComponents(extractor.getLinkElements())
// Set script and style tags at the end of the document to parse the assets.
setPostBodyComponents([...extractor.getScriptElements(), ...extractor.getStyleElements()])
// Reset collected chunks after each page is rendered
extractor.chunks = []
}
If you have DEV_SSR enabled, you should not add stage === "develop-html". Otherwise, you are good.
Hope this summary of the issue's comments help to get you started.

How to load props asynchronously for functional component in React

I am trying to speed up my application and in order to do this, I am lazy loading some components. One of my functional component loads from a .json file and it would be nice to lazy load it as well, but I am not sure how to do it. My code currently looks like this:
import React from 'react';
import Presentation from "../Presentation";
const data = () => import("../assets/sample.json");
const Finder = (props) => {
const {prop1} = props;
return ( <Presentation data={data} selection={prop1} /> );
};
export default Presentation;
When trying to render the Presentation component, it fails with an error (data undefined). How could I lazy load the json file? Is it possible using the react-loadable library? Thanks!

Categories