How do you configure SSR with Loadable Components on NextJS? - javascript

We have a requirement to use Loadable Components in a new NextJS app we are building. I believe in NextJS for this use case you tend to use their native dynamic import feature but we are pulling in code from our mature codebase with extensive use of 'Loadable Components' throughout so replacement with dynamic imports is pretty impractical (PR is here in our main codebase: https://github.com/bbc/simorgh/pull/10305).
I have put together a representive example in a repo to demonstrate an issue we are having: https://github.com/andrewscfc/nextjs-loadable
In this example I have introduced a loadable component to split the Layout component into its own bundle:
import * as React from 'react';
import Link from 'next/link';
import loadable from '#loadable/component';
const LayoutLoadable = loadable(() => import('../components/Layout'));
const IndexPage = () => (
<LayoutLoadable title="Home | Next.js + TypeScript Example">
<h1>Hello Next.js 👋</h1>
<p>
<Link href="/about">
<a>About</a>
</Link>
</p>
</LayoutLoadable>
);
export default IndexPage;
You can run this repo by running:
yarn && yarn dev (or equivalent npm commands)
If you navigate to http://localhost:3000/ the page body looks like this:
<body>
<div id="__next" data-reactroot=""></div>
<script src="/_next/static/chunks/react-refresh.js?ts=1663916845500"></script>
<script id="__NEXT_DATA__" type="application/json">
{
"props": { "pageProps": {} },
"page": "/",
"query": {},
"buildId": "development",
"nextExport": true,
"autoExport": true,
"isFallback": false,
"scriptLoader": []
}
</script>
</body>
Notice there is no html in the body other than the root div the clientside app is hydrated into: <div id="__next" data-reactroot=""></div>
The SSR is not working correctly but the app does hydrate and show in the browser so the clientside render works.
If you then change to a regular import:
import * as React from 'react';
import Link from 'next/link';
import Layout from '../components/Layout';
const IndexPage = () => (
<Layout title="Home | Next.js + TypeScript Example">
<h1>Hello Next.js 👋</h1>
<p>
<Link href="/about">
<a>About</a>
</Link>
</p>
</Layout>
);
export default IndexPage;
The body SSRs correctly:
body>
<div id="__next" data-reactroot="">
<div>
<header>
<nav>
Home
<!-- -->|<!-- -->
About
<!-- -->|<!-- -->
Users List
<!-- -->| Users API
</nav>
</header>
<h1>Hello Next.js 👋</h1>
<p>About</p>
<footer>
<hr />
<span>I'm here to stay (Footer)</span>
</footer>
</div>
</div>
<script src="/_next/static/chunks/react-refresh.js?ts=1663917155976"></script>
<script id="__NEXT_DATA__" type="application/json">
{
"props": { "pageProps": {} },
"page": "/",
"query": {},
"buildId": "development",
"nextExport": true,
"autoExport": true,
"isFallback": false,
"scriptLoader": []
}
</script>
</body>
I have attempted to configure SSR as per Loadable Component's documentation in a custom _document file:
import Document, { Html, Head, Main, NextScript } from 'next/document';
import * as React from 'react';
import { ChunkExtractor } from '#loadable/server';
import path from 'path';
export default class AppDocument extends Document {
render() {
const statsFile = path.resolve('.next/loadable-stats.json');
const chunkExtractor = new ChunkExtractor({
statsFile,
});
return chunkExtractor.collectChunks(
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
This is not working correctly and imagine it maybe because there is no where to call renderToString(jsx) as per their docs; I think this call happens internally to NextJS.
Has anyone successfully configured loadable components in NextJS with SSR? I can't seem to find the right place to apply Loadable Component's SSR instructions?

Related

Bundle React library to UMD using a modern bundler

I have a Typescript react component/library
// src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import { Image, Shimmer } from 'react-shimmer';
function App() {
return (
<div>
<Image
src="https://source.unsplash.com/random/800x600"
fallback={<Shimmer width={800} height={600} />}
/>
</div>
);
}
const render = (id: string) => ReactDOM.render(<App />, document.getElementById(id));
export default render;
I want it to be compiled to UMD format, to be consumed in HTML
// index.html
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script type="text/javascript" src="./compiled.js">
render('app');
</script>
</body>
</html>
Preferably I want to use a modern bundler like tsup. Because webpack just scares me.
Is this possible?

Next JS Issue with data from getInitialProps in _app.js "TypeError: Cannot read properties of undefined"

I'm having an issue displaying data pulled in (from Prismic) with getInitialProps in the _app.js file. I have followed the Prismic slice machine tutorial, which includes defining a header and navigation in Prismic and displaying that data on the frontend - that all works fine.
I've defined a footer now, have included the call for the footer data in the same place and way I have for the header data in the _app.js file, but that data does not display on the frontend. The error message I am seeing is:
TypeError: Cannot read properties of undefined (reading 'data')
and references the call stack to my _document.js file, but I cannot understand where the issue is in that file.
_document.js:
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { repoName } from '../prismicConfiguration'
import Link from 'next/link'
export default class extends Document {
static async getInitialProps(ctx) {
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render() {
return (
<Html lang="en">
<Head>
<script async defer src={`//static.cdn.prismic.io/prismic.js?repo=${repoName}&new=true`} />
</Head>
<body className="">
<Main />
<NextScript />
</body>
</Html>
)
}
}
_app.js:
import React from 'react'
import NextApp from 'next/app'
import { Client } from '../utils/prismicHelpers'
import '../styles/style.scss'
export default class MyApp extends NextApp {
static async getInitialProps(appCtx) {
const menu = await Client().getSingle('menu') || null
const footer = await Client().getSingle('footer') || null
console.log("FOOTER", footer)
console.log("MENU",menu)
return {
props: {
menu: menu,
footer: footer
},
}
}
render() {
const { Component, pageProps, props } = this.props
return (
<Component {...pageProps} menu={props.menu} footer={props.footer} />
)
}
}
Footer.js (where the data should be getting displayed):
import React from 'react'
import Link from 'next/link'
// Project functions
import { linkResolver } from '../prismicConfiguration'
import { RichText } from 'prismic-reactjs'
// Footer component
export default function Footer({ footer }) {
return (
<footer className="footer">
<div className="container max-width-lg">
{footer.data.eyebrow}
<RichText render={footer.data.headline} />
<RichText render={footer.data.description} />
<Link href={linkResolver(footer.data.link)}>
<a>
{footer.data.linkLabel}
</a>
</Link>
{ footer.data.copyrightText }
{ footer.data.signoffText }
</div>
</footer>
)
}
I'm so confused as to why this footer data cannot be displayed or read when it has been defined and called in the same way as the header, which works completely fine. I am console logging both sets of data in the _app.js file and both are returning fine in the terminal, so I am confident what I am adding to the footer component file is correct. For some context, the reason I am pulling this data in the _app.js file is that its data that needs to be across all pages rather than calling it on every single page.
Where am I going wrong here?
Thanks
Addition:
The Footer component is being added to a layout component:
import React from 'react'
import { useEffect } from 'react'
import Head from 'next/head'
import { useRouter } from 'next/router'
import Script from 'next/Script'
// Components
import Header from './Header'
import Footer from './Footer'
// Project functions
import * as gtag from '../lib/gtag'
// Layout component
const Layout = ({ children, footer, menu }) => {
const router = useRouter()
useEffect(() => {
const handleRouteChange = (url) => {
gtag.pageview(url)
}
router.events.on('routeChangeComplete', handleRouteChange)
return () => {
router.events.off('routeChangeComplete', handleRouteChange)
}
}, [router.events])
return (
<div className="page_wrapper">
<Head>
{/* <meta charSet="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon/favicon.ico" />
<script dangerouslySetInnerHTML={{ __html: `document.getElementsByTagName("html")[0].className += " js";`}}
/> */}
{/* <Link
rel="preload"
href="/fonts/font-file.woff2"
as="font"
crossOrigin=""
/> */}
</Head>
<Script
id="codyhouse-utils-js"
src="https://unpkg.com/codyhouse-framework/main/assets/js/util.js"
strategy="beforeInteractive"
/>
<Script
strategy="afterInteractive"
src={`https://www.googletagmanager.com/gtag/js?id=${gtag.GA_TRACKING_ID}`}
/>
<Script
id="gtag-init"
strategy="afterInteractive"
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${gtag.GA_TRACKING_ID}', {
page_path: window.location.pathname,
});
`,
}}
/>
{/* <Script
src="https://www.google.com/recaptcha/api.js?&render=explicit"
strategy="afterInteractive"
/> */}
<Header menu={menu} />
<main>
{children}
</main>
<Footer footer={footer} />
</div>
);
};
export default Layout

can't find module - react

I just want to link the files but it's not working. I am not sure what I am doing wrong. Could you please let me know. This is my first day using react. So, please forgive. I am following this gatsby tutorial.
gatsbyjs.com/docs/tutorial/part-2/
js
import * as React from 'react'
import { Link } from 'gatsby'
const IndexPage = () => {
return (
<main>
<title>Home Page</title>
<h1>Welcome to my Gatsby site!</h1>
<Link to="/about">About</Link>
<p>I'm making this by following the Gatsby Tutorial.</p>
</main>
)
}
export default IndexPage
js second page
import * as React from 'react'
import { Link } from 'gatsby'
const AboutPage = () => {
return (
<main>
<title>About Me</title>
<h1>About Me</h1>
<Link to="/">Back to Home</Link>
<p>Hi there! I'm the proud creator of this site, which I built with Gatsby.</p>
</main>
)
}
export default AboutPage
Try import React from 'react' instead

How to defer load render blocking css in next.js?

As you can see in the below next.js code I am trying to defer load the render blocking css by giving my main.css file path in href attribute but I am struggling to do it in next.js. What I want is after loading the critical css in _document.js tag under tag, to load the non-critical css which is not above the fold.
_app.js
import App from "next/app"
import Head from "next/head"
import React from "react"
import { observer, Provider } from 'mobx-react'
import Layout from "../components/Layout"
import allStores from '../store'
export default class MyApp extends App {
componentDidMount = () => {
};
render() {
const { Component, pageProps, header, footer, } = this.props
return (
<>
<Head >
<link rel="preload" href="path/to/main.css" as="style"
onLoad="this.onload=null;this.rel='stylesheet'"></link>
</Head>
<Provider {...allStores}>
<Layout header={header} footer={footer}>
<Component {...pageProps} />
</Layout>
</Provider>
</>
)
}
}
as #chrishrtmn said at _document.js you can do like this:
import Document, { Main, NextScript } from 'next/document';
import { CriticalCss } from '../components/CriticalCss';
class NextDocument extends Document {
render() {
return (
<html>
<CriticalCssHead />
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}
export default NextDocument;
as in your component you can put the CSS:
import { readFileSync } from 'fs';
import { join } from 'path';
export interface Props {
assetPrefix?: string;
file: string;
nonce?: string;
}
export const InlineStyle: React.FC<Props> = ({ assetPrefix, file, nonce }) => {
const cssPath = join(process.cwd(), '.next', file);
const cssSource = readFileSync(cssPath, 'utf-8');
const html = { __html: cssSource };
const id = `${assetPrefix}/_next/${file}`;
return <style dangerouslySetInnerHTML={html} data-href={id} nonce={nonce} />;
};
I got the source for this code from the current repo:
https://github.com/JamieMason/nextjs-typescript-tailwind-critical-css
have a look here
https://github.com/JamieMason/nextjs-typescript-tailwind-critical-css/tree/master/components/CriticalCssHead
Here's my current favorite solution, sourced from here:
https://github.com/facebook/react/issues/12014#issuecomment-434534770
It results in two empty <script></script> tags in your head, but works.
<script
dangerouslySetInnerHTML={{
__html: `</script><link rel='preload' href='style.css' as='style' onload="this.onload=null;this.rel='stylesheet'"/><script>`,
}}
/>
The Next.js team indicated that a similar strategy is possible with their component, but in practice, I was getting compilation errors:
https://github.com/vercel/next.js/issues/8478#issuecomment-524332188
The error I received was:
Error: Can only set one of children or props.dangerouslySetInnerHTML.
Might have to move the <Head> into _document.js instead of _app.js according to the documentation.

React File Import Issue

I'm very new to React and ran into an issue when trying to import a "sub-component", for lack of a better word.
In my App.js file I imported my header class: import Header from './Components/Header/Header'; Which worked fine.
Within my Header.js file I'm using router to select different components. However, when I try to import my Home class: import Home from '../Subcomponents/HomePage/HomePage'; I receive the following error: Module not found: Can't resolve '../Subcomponents/HomePage/HomePage'
My file structure is:
app.js
Components/Header/Header.js
Subcomponents/HomePage/HomePage.js
App.js Code:
import React, { Component } from 'react';
import Header from './Components/Header/Header';
import Footer from './Components/Footer/Footer';
import Body from './Components/Body/Body';
import './Assets/css/mainCSS.css';
class App extends Component {
render() {
return (
<div className="App">
<Header />
<Body />
<Footer/>
</div>
);
}
}
export default App;
Header Code:
import React from 'react';
import Home from '../Subcomponents/HomePage/HomePage';
import { Router, Route, Link } from 'react-router-dom';
const header = () => {
return <header>
<Router>
<nav>
<ul>
<li>
<Link to='/'>Home</Link>
</li>
</ul>
<hr />
<Route excat path ="/" component={Home} />
</nav>
</Router>
</header>
}
export default header;
HomePage Code:
import React from 'react';
const homepage =() =>{
return <p>
homepage working
</p>
}
export default homepage;
Am I doing something wrong here or is this not possible in React? Any advice would be greatly appreciated!
Thanks!
From Header.js, ../ puts you into Components, not into the parent. It should be '../../Subcomponents/HomePage/HomePage'.
Also, imho: within each component folder, name the file index.js so that it will be automatically exported. Than you can just do: '../../Subcomponents/HomePage'

Categories