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.
Related
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
I'm working on my first Material UI - CSS Modules - Next.js project.
Problem:
When I disable JS in my chrome dev tools, the style is not applied.
Now, I don't understand if this has something to do with Material UI? Or maybe with the way I'm importing styles?
I couldn't find any answer by now. Any help is appreciated. Thanks a lot!!
Here is some relevant code:
//pages/_document.js
import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheets } from '#material-ui/core/styles';
import theme from './_theme';
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
<meta name="theme-color" content={theme.palette.primary.main} />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
MyDocument.getInitialProps = async (ctx) => {
const sheets = new ServerStyleSheets();
const originalRenderPage = ctx.renderPage;
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => sheets.collect(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
};
};
//pages/_app.js
import React from "react";
import "../styles/global.css";
import { ThemeProvider } from "#material-ui/core/styles";
import theme from "./_theme";
import CssBaseline from "#material-ui/core/CssBaseline";
function MyApp({ Component, pageProps }) {
React.useEffect(() => {
const jssStyles = document.querySelector("#jss-server-side");
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}, []);
return (
<>
<ThemeProvider theme={theme}>
<CssBaseline>
<Component {...pageProps} />
</CssBaseline>
</ThemeProvider>
</>
);
}
export default MyApp;
//componentXYZ.js -> I import styles like this
import Input from "#material-ui/core/Input"; //from material ui
import styles from "./componentXYZ.module.css //other styles
The problem is not related to your code
The Next.js docs states this :
"If you disable JavaScript the CSS will still be loaded in the production build (next start). During development, we require JavaScript to be enabled to provide the best developer experience with Fast Refresh."
Doc reference
So I am playing around with my first Nextjs app and am having trouble with adding a persistent sidebar.
I found Adam Wathan's article on persistent layouts in nextjs, but it seems like there is a newer pattern that was added recently using the _app.js page. I went to the docs and a few of the github issues around it, but it doesn't looks like there's a lot of documentation around it yet.
So for my example, I have my _app.js file:
import '../css/tailwind.css'
import Head from 'next/head'
import Sidebar from "../components/Sidebar";
export default function App({Component, pageProps}){
return(
<>
<Head />
<Component {...pageProps} />
</>
)
}
import React, { useState } from "react";
import Transition from "../components/Transition";
import Link from 'next/link'
function Sidebar({ children }) {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [hideSidebarMenu, setHideSidebarMenu] = useState(true);
const openSidebar = () => {
setIsSidebarOpen(true);
setHideSidebarMenu(false);
};
const closeSidebar = () => {
setIsSidebarOpen(false);
};
const hideSidebar = () => {
setHideSidebarMenu(true);
};
return(
<div>
/*sidebar links here*/
</div>
)
}
export default Sidebar;
How do I integrate my sidebar component into this? I've tried adding it next to component and wrapping component and a few other iterations with no luck. Hopefully I'm just missing something simple.
This is odd. I could have swore I tried this very simple solution before, but something like this was completely sufficient.
This solution will feed in the page that you are on using the children prop in the sidebar.
import '../css/tailwind.css'
import Head from 'next/head'
import Sidebar from "../components/Sidebar";
export default function App({Component, pageProps}){
return(
<>
<Head />
<Sidebar >
<Component {...pageProps} />
</Sidebar>
</>
)
}
this option will just render the sidebar along with the content
import '../css/tailwind.css'
import Head from 'next/head'
import Sidebar from "../components/Sidebar";
export default function App({Component, pageProps}){
return(
<>
<Head />
<Sidebar />
<Component {...pageProps} />
</>
)
}
I have a next js application and I need to integrate it with an existing site by importing the header and footer from the parent site. It the markup along with supporting libs are being delivered through a JS file, one for each header and footer respectively. This is how my _apps.js, navigation.js and footer.js file looks like:
_app.js:
render() {
return (
<Provider store={reduxStore}>
<Head headerData={newStaticData} />
<Navigation />
<OtherComponents />
<Footer />
</Provider>
);
}
navigation.js:
class Navigation extends Component {
shouldComponentUpdate() {
return false;
}
componentDidMount() {
const script = document.createElement('script');
script.src = "https://mainsite.com/external/header.js";
script.async = true
document.body.appendChild(script);
}
render() {
return (
<div id="target_div_id"></div>
)
}
}
export default Navigation;
footer.js:
class Footer extends Component {
shouldComponentUpdate() {
return false;
}
componentDidMount() {
const script = document.createElement('script');
script.src = "https://mainsite.com/external/footer.js";
script.async = true
document.body.appendChild(script);
}
render() {
return (
<div id="footer_target_id"></div>
)
}
}
export default Footer;
When I run this code, just the main navigation will appear but not the footer. If it comment out the navigation, then the footer will appear. I am not sure why but it looks like only one loads at a time. I have tried using script.defer=true but hasn't helped either.
Can anyone advice what might be causing this and what's the resolution?
TIA.
you can easily do this with react-helmet even in child component
import React from "react";
import {Helmet} from "react-helmet";
class Navigation extends Component {
render() {
return (
<div id="target_div_id">
<Helmet>
<script type="text/javascript" href="https://mainsite.com/external/header.js" />
</Helmet></div>
)
}
}
export default Navigation;
try you use react hooks instead of react Component lifecycle
const Navigation = ()=> {
return (
<div id="target_div_id">
<Helmet>
<script type="text/javascript" href="https://mainsite.com/external/header.js" />
</Helmet></div>
)
}
export {Navigation} ;
// parent
import {Navigation} from "../Navigation.js";
I would suggest you not to use _app.js for this.
Try creating a Layout file like below:
// MainLayout.js
import NavBar from './Navigation.js'
import Footer from './Footer.js'
export default ({ children }) => (
<>
<Navigation />
{children}
<Footer />
</>
)
And have your main file as like this:
// index.js
import React from 'react'
import MainLayout from '../components/Layouts/MainLayout.js'
import Banner from '../components/Banner/Banner.js'
export default function Home() {
return (
<>
<MainLayout>
<Banner />
</MainLayout>
</>
)
}
I'm trying to use styled components with next.js. I've added the babel plugin and added a custom _document.js. That all seems to be working fine.
However, when I try and use isomorphic-unfetch to getInitialProps into the page, the request returns data, but it doesn't make it into Props.
For _document.js I'm using the code from the next.js site here and have also tried a slightly different version from here which I also saw on the next.js docs site at some point.
My test page looks like this (also from the next.js docs):
import styled from 'styled-components';
import fetch from 'isomorphic-unfetch';
export default class MyPage extends React.Component {
static async getInitialProps({ req }) {
const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
return { userAgent }
}
render() {
return (
<div>
Hello World {this.props.userAgent}
</div>
)
}
}
I also have an _app.js that looks like this:
import App, { Container } from 'next/app';
import Page from '../components/Page';
class MyApp extends App {
render() {
const { Component } = this.props;
return (
<Container>
<Page>
<Component />
</Page>
</Container>
);
}
}
export default MyApp;
And Page.js just has some styled components and a theme with a component that looks like this:
class Page extends Component {
render() {
return (
<ThemeProvider theme={theme}>
<StyledPage>
<GlobalStyle />
<Meta />
<Header />
{this.props.children}
</StyledPage>
</ThemeProvider>
);
}
}
export default Page;
I feel like it's something to do with the new _document.js or _app.js not passing the props down somehow.
Clearly I'm pretty new to next.js, so apologies if it's something stupid. In the meantime, I'll keep plugging away to get a better understanding of what's going on. I wanted to ask in parallel here since I'm under some time pressure for this project.
Many thanks for any thoughts you might have.
Not long after posting, I worked out the issue. I guess posting on SO helps to understand the issue.
Essentially it was _app.js which was the problem, not _document.js as I had initially expected.
I needed to add pageProps to _app.js so that they were propagated down to the page:
import App, { Container } from 'next/app';
import Page from '../components/Page';
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
}
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<Page>
<Component {...pageProps} />
</Page>
</Container>
);
}
}
export default MyApp;