I'm trying to implement styled-components in a React project with Nextjs. The problem is that, although I can implement and see the styles, there is a small delay when I see it on the browser. First it loadeds the component without style, and 22ms later the style is applied. What I'm doing wrong?
Thanks
Here is my code!
pages/index.js
import React from "react";
import Home from "../components/home/index";
const index = () => {
return (
<React.Fragment>
<Home />
</React.Fragment>
);
};
export default index;
components/home.js
import React from "react";
import styled from 'styled-components';
const Title = styled.h1`
color: red;
`;
function Home() {
return (
<div>
<Title>My First Next.js Page</Title>
</div>
);
}
export default Home;
babel.rc
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
}
pages/_document.js
import Document from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: App => props => sheet.collectStyles(<App {...props} />)
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
)
};
} finally {
sheet.seal();
}
}
}
This happens because your styles are being applied client side. You will need to follow this modification from the examples provided by Next.js.
You actually need to create a custom Document, collect all your styles from your <App /> component using ServerStyleSheet provided by styled-components and apply them server side, so when your app reaches the client, the styles will already be there.
As they also state on the README of this example:
For this purpose we are extending the <Document /> and injecting the server side rendered styles into the <head>, and also adding the babel-plugin-styled-components (which is required for server side rendering).
I hope this solves your issue.
Here is an example of the _document file:
import Document, { Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static getInitialProps({ renderPage }) {
const sheet = new ServerStyleSheet();
function handleCollectStyles(App) {
return props => {
return sheet.collectStyles(<App {...props} />);
};
}
const page = renderPage(App => handleCollectStyles(App));
const styleTags = sheet.getStyleElement();
return { ...page, styleTags };
}
render() {
return (
<html>
<Head>{this.props.styleTags}</Head>
<body>
<Main />
<NextScript />
</body>
</html>
);
}
}
I hope this helps!
Related
I'm trying out Next.js 13's new experimental appDir, and I've run into a bit of a snag.
This project is built on:
Next.js 13
React 18
MUI 5 (styled components, which use #mui/system #emotion/react #emotion/styled)
To get MUI/Emotion to play nice with server-side rendering, there is an open ticket to handle the SSR cache, which suggests that I have to turn the layout.tsx into a client-only component. This means things like <head> will only render server-side now.
To work around that, I've split the layout into a server component with client leaf.
But no matter what I do, when I deploy on Vercel, I can't get <title> or any <meta> tags to appear in the <head> in the server source.
Here's an extract of what I've done. (Note: needs experimental: { appDir: true } in next.config.js).
// app/head.tsx
export default function Head() {
return (
<>
<title>My App</title>
</>
)
}
// app/layout.tsx
import { SSRThemeProvider } from "lib/SSR/theme";
import React from "react";
import theme from "styles/appTheme";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<React.StrictMode>
<html lang="en" key="root">
<head />
</html>
{/* ^ duplicate html/head outside of client-only provider */}
<SSRThemeProvider theme={theme} key="theme-provider">
<html lang="en" key="root">
{/*
<head /> will contain the components returned by the nearest parent
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
*/}
<head />
<body>{children}</body>
</html>
</SSRThemeProvider>
</React.StrictMode>
);
}
// app/home.tsx
import { HomePage } from "content/home";
import { getPlaceholderImages } from "lib/SSR/placeholderImages";
export default async function HybridHome() {
const images = await getPlaceholderImages();
// ^ server-side stuff with sharp
return <HomePage placeholderImages={images} />;
}
"use client";
// ^ client only directive
// lib/SSR/theme.provider.tsx
import { ThemeProvider } from "#mui/material";
import { DefaultTheme, ThemeProviderProps } from "#mui/styles";
import React from "react";
export const SSRThemeProvider = <T = DefaultTheme,>(
props: ThemeProviderProps<T>
): React.ReactElement<ThemeProviderProps<T>> => {
return (
<React.StrictMode>
<ThemeProvider {...props} />
</React.StrictMode>
);
};
"use client";
// ^ client only here too
// content/home.tsx
import { CssBaseline } from "#mui/material";
import RootStyleRegistry from "lib/SSR/emotion";
import { usePathname, useSearchParams } from "next/navigation";
export const HomePage = () => {
const pathname = usePathname();
const searchParams = useSearchParams();
return (
<>
<CssBaseline key="css-baseline" />
<RootStyleRegistry>
<main>
{/* Words go here /*}
</main>
</RootStyleRegistry>
</>
);
};
Your head.tsx component should return a fragment, not head.
Try this
return (
<>
<title>My App</title>
</>
)
}
Secondly, you don't need to import this component anywhere. It is automatically added to the head section of corresponding page and sub-pages.
Ah, turns out this is an actual bug in useQueryParams per https://github.com/vercel/next.js/issues/44868.
I replaced it temporarily with a custom hook instead, and now all my tags show up, even when I use #MayankKumar's suggestion and use <head /> directly without another component.
Here's my hook while that ticket remains open:
import { usePathname } from "next/navigation";
import { useEffect, useState } from "react";
import isEqual from "react-fast-compare";
interface UrlEvents {
routeChanged?: ({
pathname,
location,
}: {
pathname: string | null;
location: Location | null;
}) => void;
}
export const useNavigation = ({ on }: { on?: UrlEvents }) => {
const pathname = usePathname();
const [location, setLocation] = useState<Location>();
const { routeChanged } = on || {};
useEffect(() => {
if (pathname && typeof window !== "undefined") {
window.addEventListener("popstate", () => {
if (!isEqual(window.location, location)) {
setLocation(window.location);
routeChanged?.({
pathname,
location: window.location,
});
}
});
return () => {
window.removeEventListener("popstate", () => {});
};
}
}, [location, pathname, routeChanged]);
return location;
};
I bought this template and am trying to understand which page getLayout in this code"{getLayout(<Component {...pageProps} />)}" goes to on initial load. I'm guessing it's a global variable somewhere, but I can't find it using the definitions. I'm trying to under the next.js documentation, but I'm having issues. If anyone has a good tutorial for this I'll happily take it.
import type { AppProps } from 'next/app';
import { appWithTranslation } from 'next-i18next';
import { SessionProvider } from 'next-auth/react';
import '#/assets/css/main.css';
import 'react-toastify/dist/ReactToastify.css';
import { ToastContainer } from 'react-toastify';
import { ModalProvider } from '#/components/ui/modal/modal.context';
import ManagedModal from '#/components/ui/modal/managed-modal';
import ManagedDrawer from '#/components/ui/drawer/managed-drawer';
import DefaultSeo from '#/components/seo/default-seo';
import { SearchProvider } from '#/components/ui/search/search.context';
import PrivateRoute from '#/lib/private-route';
import { CartProvider } from '#/store/quick-cart/cart.context';
import SocialLogin from '#/components/auth/social-login';
import { NextPageWithLayout } from '#/types';
import QueryProvider from '#/framework/client/query-provider';
import { getDirection } from '#/lib/constants';
import { useRouter } from 'next/router';
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
};
function CustomApp({
Component,
pageProps: { session, ...pageProps },
}: AppPropsWithLayout) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout ?? ((page) => page);
const authenticationRequired = Component.authenticationRequired ?? false;
const { locale } = useRouter();
const dir = getDirection(locale);
return (
<div dir={dir}>
<SessionProvider session={session}>
<QueryProvider pageProps={pageProps}>
<SearchProvider>
<ModalProvider>
<CartProvider>
<>
<DefaultSeo />
{authenticationRequired ? (
<PrivateRoute>
{getLayout(<Component {...pageProps} />)}
</PrivateRoute>
) : (
getLayout(<Component {...pageProps} />)
)}
<ManagedModal />
<ManagedDrawer />
<ToastContainer autoClose={2000} theme="colored" />
<SocialLogin />
</>
</CartProvider>
</ModalProvider>
</SearchProvider>
</QueryProvider>
</SessionProvider>
</div>
);
}
export default appWithTranslation(CustomApp);
Basically, you can define a per component layout. In order to do so, when you are defining a component, you add a property named getLayout. Here's an example, for a better understanding.
// ...
const Component: React.FC = () => <div>
I have a custom layout
</div>
// Here we define a layout, let's imagine it's a component
// we have inside /layouts and we have previously imported it
Component.getLayout = (page: React.ReactElement) =>
<LayoutComponent>{page}</LayoutComponent>
Now, when a page is rendered (note that in a Next JS app, all pages are rendered as a children of what is inside _app.{js,jsx,ts,tsx}) your code checks if the prop getLayout has been defined or not. Then, it is basically calling such function, if exists, otherwise it renders the base component.
I'm using Next.js with context API and styled components and I can't seem to get getStaticProps working.
I have read other posts and often they talk about the custom _app which I do have but I never ran into the issue before using context API. I have also tried the getInitialProps function and to no avail.
I should also note that even after not including the context wrapper I don't get a response from a function so I'm not at all sure of where to look.
Here is my code. Can you see what's going on?
import React from 'react';
import fetch from 'node-fetch';
export default function Header(props) {
console.log(props.hi);
return <div>Hey dis header</div>;
}
export async function getStaticProps(context) {
return {
props: {
hi: 'hello',
},
};
}
I have tried logging from the function but nothing is logging so I would imagine the problem is that the function isn't running for whatever reason.
Heres my custom _app file
import { GlobalContextWrapper } from 'context';
import Header from 'components/header/Header';
import App from 'next/app';
function MyApp({ Component, pageProps }) {
return (
<GlobalContextWrapper>
<Header />
<Component {...pageProps} />
<p>footer</p>
</GlobalContextWrapper>
);
}
MyApp.getInitialProps = async (appContext) => {
// calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext);
return { ...appProps };
};
export default MyApp;
Here is my context file
import { useReducer } from 'react';
import initialState from './intialState';
import reducer from './reducer';
import GlobalStyle from '../GlobalStyle';
import theme from '../theme';
import { ThemeProvider } from 'styled-components';
export const GlobalContext = React.createContext();
export function GlobalContextWrapper({ children }) {
const [globalState, dispatch] = useReducer(reducer, initialState);
return (
<GlobalContext.Provider value={{ globalState, dispatch }}>
<GlobalStyle />
<ThemeProvider theme={theme}>{children}</ThemeProvider>
</GlobalContext.Provider>
);
}
The issue was that i was not exporting this function from a page but instead a component and a custom app file.
Does anyone know a way i can get around this? The problem is that i have a header that gets data from a response and i want this header to be shown on every page without having to manually add it to each page along with repeating the getStaticProps function
A solution based on your code is just getting data in your _app.js - getInitialProps and pass to the Header
function MyApp({ Component, pageProps }) {
return (
<GlobalContextWrapper>
<Header data={pageProps.header}/>
<Component {...pageProps} />
<p>footer</p>
</GlobalContextWrapper>
);
}
MyApp.getInitialProps = async (appContext) => {
// calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext);
const headerData = ....
return { ...appProps, header: headerData };
};
I need to access the context API in my _app.js file in order to set global state triggered by the router events. The reason for this is to set a loading state which can be accessed by individual components throughout the app. The problem is is the context is provided from the _app.js file, so I don't have the context's context as it were.
context.js
import React, { createContext, useState } from "react";
export const Context = createContext();
const ContextProvider = (props) => {
const [isLoading, setIsLoading] = useState(false);
return (
<Context.Provider
value={{
isLoading,
setIsLoading,
}}
>
{props.children}
</Context.Provider>
);
};
export default ContextProvider;
_app.js
import React, { useContext } from "react";
import App from "next/app";
import Head from "next/head";
import Aux from "../hoc/Aux";
import ContextProvider, { Context } from "../context/context";
import { withRouter } from "next/router";
class MyApp extends App {
static contextType = Context;
componentDidMount() {
this.props.router.events.on("routeChangeStart", () => {
this.context.isLoading(true);
});
this.props.router.events.on("routeChangeComplete", () => {
this.context.isLoading(false);
});
this.props.router.events.on("routeChangeError", () => {
this.context.isLoading(false);
});
}
render() {
const { Component, pageProps } = this.props;
return (
<Aux>
<Head>
<title>My App</title>
</Head>
<ContextProvider>
<Component {...pageProps} />
</ContextProvider>
</Aux>
);
}
}
export default withRouter(MyApp);
Clearly this wouldn't work since _app.js is not wrapped in the context provider. I've tried moving the router event listeners further down the component tree, but then I don't get the loading state from my home page to my dynamically created pages that way.
Is there any workaround that lets me consume context in _app.js? I can't think of any other way I can access loading state globally to conditionally load specific components.
It's not clear to me why you need the context provider to be a parent of _app.js (or a separate component at all). Wouldn't the following work?
class MyApp extends App {
state = {
isLoading: false,
};
componentDidMount() {
this.props.router.events.on("routeChangeStart", () => {
this.setIsLoading(true);
});
this.props.router.events.on("routeChangeComplete", () => {
this.setIsLoading(false);
});
this.props.router.events.on("routeChangeError", () => {
this.setIsLoading(false);
});
}
render() {
const { Component, pageProps } = this.props;
return (
<Aux>
<Head>
<title>My App</title>
</Head>
<Context.Provider
value={{
isLoading: this.state.isLoading,
setIsLoading: this.setIsLoading,
}}>
<Component {...pageProps} />
</Context.Provider>
</Aux>
);
}
setIsLoading = (isLoading) => {
this.setState({ isLoading });
}
}
export default withRouter(MyApp);
Alternatively (if there's something I'm really not understanding about your use case), you could create a HoC:
function withContext(Component) {
return (props) => (
<ContextProvider>
<Component {...props} />
</ContextProvider>
);
}
class MyApp extends App {
...
}
export default withContext(withRouter(MyApp));
You can show a loading indicator using nprogress. For example:
import NProgress from "nprogress";
import Router from "next/router";
Router.onRouteChangeStart = () => NProgress.start();
Router.onRouteChangeComplete = () => NProgress.done();
Router.onRouteChangeError = () => NProgress.done();
Source
I add header component to layout but i don't want to send props from layout to header for every page I want use getInitialProps
layout.js
import Header from './header'
export default ({title}) => (
<div>
<Head>
<title>{ title }</title>
<meta charSet='utf-8' />
</Head>
<Header />
{children}
</div>
)
header.js
export default class Header extends Component {
static async getInitialProps () {
const headerResponse = await fetch(someapi)
return headerResponse;
}
render() {
console.log({props: this.props})
return (
<div></div>
);
}
}
console: props: {}
App.js
import Layout from './layout'
import Page from './page'
import axios from 'axios'
const app = (props) => (
<Layout >
<Page {...props}/>
</Layout>
)
app.getInitialProps = async function(){
try {
const response = await axios.get(someUrl)
return response.data;
} catch(e) {
throw new Error(e);
}
}
export default app
I want to use get initial props in Header component but it not firing
EDIT: 2021 way of doing this :
// with typescript remove type if you need a pure javascript solution
// _app.tsx or _app.jsx
import type { AppProps } from 'next/app';
import Layout from '../components/Layout';
export default function App({ Component, pageProps }: AppProps) {
return (
<Layout {...pageProps}>
<Component {...pageProps} />
</Layout>
);
}
// In order to pass props from your component you may need either `getStaticProps` or `getServerSideProps`.
// You should definitely not do that inside your `_app.tsx` to avoid rendering your whole app in SSR;
// Example for SSR
export async function getServerSideProps() {
// Fetch data from external API
const res = await fetch(`https://.../data`)
const data = await res.json()
// Pass data to the page via props
return { props: { data } }
}
Next.js uses the App component to initialize pages. You can override it and control the page initialization.
What you could do is, put your logic inside the _app override and pass it to your children components, example with a Layout component.
Create a page/_app.js
import React from 'react'
import App, { Container } from 'next/app'
import Layout from '../components/Layout'
export default class MyApp extends App {
static async getInitialProps({ Component, router, ctx }) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
/* your own logic */
return { pageProps }
}
render () {
const { Component, pageProps } = this.props
return (
<Container>
<Layout {...pageProps}>
<Component {...pageProps} />
</Layout>
</Container>
)
}
}
There is a good example from zeit at github