Multiple preact-router routes rendered at the same time in SSR (on Vercel) - preact

If you go to my Preact app on https://startupguide.vercel.app/ and click “Name Generator”, you will see only the Name Generator form (as it should be). But if you go to https://startupguide.vercel.app/namegenerator and refresh the page (to get SSR page), you see first the Name Generator form, AND the Start page underneath(!).
This is how I have set up my Preact routes:
import { Router } from 'preact-router'
import Start from './pages/Start'
import NameGenerator from './pages/NameGenerator'
const App = ({ prop }) => {
return (
<Router>
<Start path='/' />
<NameGenerator path='/namegenerator' />
<NameGenerator path='/namegenerator/:word1' />
<NameGenerator path='/namegenerator/:word1/:word2' />
</Router>
)
}
export default App
What could be the problem?

The /namegenerator page isn't actually being server-rendered. If you disable JavaScript and load the page, you'll see that it's just the homepage content. This causes an SSR mismatch, which breaks hydration.
To fix this, you can prerender the namegenerator page by creating a prerender-urls.json file:
[
{
"url": "/",
"title": "Amazing Startup Guide"
},
{
"url": "/namegenerator",
"title": "Name Generator – Amazing Startup Guide"
}
]
Then update your package.json "build" script to pass that file as --prerenderUrls:
"scripts": {
"build": "preact build --prerenderUrls ./prerender-urls.json",
...
}
Pre-rendering docs are here: https://preactjs.com/cli/pre-rendering/#multiple-urls-and-custom-data

The #JasonMiller's answer is the correct one. But I'll leave mine because it's answers an issue with quite similar symptoms, and can be useful for someone else in the future.
You are most probably doing something like
render(<App/>, root)
while you should do something like:
render(<App/>, root, root.firstElementChild)
for more details you can read
https://github.com/preactjs/preact/issues/1060
and
https://github.com/preactjs/preact/issues/24

Related

Linking to a different url using Link of react router dom [duplicate]

Since I'm using React Router to handle my routes in a React app, I'm curious if there is a way to redirect to an external resource.
Say someone hits:
example.com/privacy-policy
I would like it to redirect to:
example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies
I'm finding exactly zero help in avoiding writing it in plain JavaScript at my index.html loading with something like:
if (window.location.path === "privacy-policy"){
window.location = "example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies"
}
Here's a one-liner for using React Router to redirect to an external link:
<Route path='/privacy-policy' component={() => {
window.location.href = 'https://example.com/1234';
return null;
}}/>
It uses the React pure component concept to reduce the component's code to a single function that, instead of rendering anything, redirects browser to an external URL.
It works both on React Router 3 and 4.
With Link component of react-router you can do that. In the "to" prop you can specify 3 types of data:
a string: A string representation of the Link location, created by concatenating the location’s pathname, search, and hash properties.
an object: An object that can have any of the following properties:
pathname: A string representing the path to link to.
search: A string representation of query parameters.
hash: A hash to put in the URL, e.g. #a-hash.
state: State to persist to the location.
a function: A function to which current location is passed as an argument and which should return location representation as a string or as an object
For your example (external link):
https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies
You can do the following:
<Link to={{ pathname: "https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies" }} target="_blank" />
You can also pass props you’d like to be on the such as a title, id, className, etc.
There isn’t any need to use the <Link /> component from React Router.
If you want to go to external link use an anchor tag.
<a target="_blank" href="https://meetflo.zendesk.com/hc/en-us/articles/230425728-Privacy-Policies">Policies</a>
It doesn't need to request React Router. This action can be done natively and it is provided by the browser.
Just use window.location.
With React Hooks
const RedirectPage = () => {
React.useEffect(() => {
window.location.replace('https://www.google.com')
}, [])
}
With React Class Component
class RedirectPage extends React.Component {
componentDidMount(){
window.location.replace('https://www.google.com')
}
}
Also, if you want to open it in a new tab:
window.open('https://www.google.com', '_blank');
I actually ended up building my own Component, <Redirect>.
It takes information from the react-router element, so I can keep it in my routes. Such as:
<Route
path="/privacy-policy"
component={ Redirect }
loc="https://meetflo.zendesk.com/hc/en-us/articles/230425728-Privacy-Policies"
/>
Here is my component in case anyone is curious:
import React, { Component } from "react";
export class Redirect extends Component {
constructor( props ){
super();
this.state = { ...props };
}
componentWillMount(){
window.location = this.state.route.loc;
}
render(){
return (<section>Redirecting...</section>);
}
}
export default Redirect;
Note: This is with react-router: 3.0.5, it is not so simple in 4.x
I went through the same issue. I want my portfolio to redirect to social media handles. Earlier I used {Link} from "react-router-dom". That was redirecting to the sub directory as here,
Link can be used for routing web pages within a website. If we want to redirect to an external link then we should use an anchor tag. Like this,
Using some of the information here, I came up with the following component which you can use within your route declarations. It's compatible with React Router v4.
It's using TypeScript, but it should be fairly straightforward to convert to native JavaScript:
interface Props {
exact?: boolean;
link: string;
path: string;
sensitive?: boolean;
strict?: boolean;
}
const ExternalRedirect: React.FC<Props> = (props: Props) => {
const { link, ...routeProps } = props;
return (
<Route
{...routeProps}
render={() => {
window.location.replace(props.link);
return null;
}}
/>
);
};
And use with:
<ExternalRedirect
exact={true}
path={'/privacy-policy'}
link={'https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies'}
/>
The simplest solution is to use a render function and change the window.location.
<Route path="/goToGoogle"
render={() => window.location = "https://www.google.com"} />
If you want a small reusable component, you can just extract it like this:
const ExternalRedirect = ({ to, ...routeProps }) => {
return <Route {...routeProps} render={() => window.location = to} />;
};
and then use it (e.g. in your router switch) like this:
<Switch>
...
<ExternalRedirect exact path="/goToGoogle" to="https://www.google.com" />
</Switch>
I had luck with this:
<Route
path="/example"
component={() => {
global.window && (global.window.location.href = 'https://example.com');
return null;
}}
/>
I solved this on my own (in my web application) by adding an anchor tag and not using anything from React Router, just a plain anchor tag with a link as you can see in the picture screenshot of using anchor tag in a React app without using React Router
Basically, you are not routing your user to another page inside your app, so you must not use the internal router, but use a normal anchor.
Although this is for a non-react-native solution, but you can try.
In React Router v6, component is unavailable. Instead, now it supports element. Make a component redirecting to the external site and add it as shown.
import * as React from 'react';
import { Routes, Route } from "react-router-dom";
function App() {
return(
<Routes>
// Redirect
<Route path="/external-link" element={<External />} />
</Routes>
);
}
function External() {
window.location.href = 'https://google.com';
return null;
}
export default App;
In React Route V6 render props were removed. It should be a redirect component.
RedirectUrl:
const RedirectUrl = ({ url }) => {
useEffect(() => {
window.location.href = url;
}, [url]);
return <h5>Redirecting...</h5>;
};
Route:
<Routes>
<Route path="/redirect" element={<RedirectUrl url="https://google.com" />} />
</Routes>
I think the best solution is to just use a plain old <a> tag. Everything else seems convoluted. React Router is designed for navigation within single page applications, so using it for anything else doesn't make a whole lot of sense. Making an entire component for something that is already built into the <a> tag seems... silly?
To expand on Alan's answer, you can create a <Route/> that redirects all <Link/>'s with "to" attributes containing 'http:' or 'https:' to the correct external resource.
Below is a working example of this which can be placed directly into your <Router>.
<Route path={['/http:', '/https:']} component={props => {
window.location.replace(props.location.pathname.substr(1)) // substr(1) removes the preceding '/'
return null
}}/>
I don't think React Router provides this support. The documentation mentions
A < Redirect > sets up a redirect to another route in your application to maintain old URLs.
You could try using something like React-Redirect instead.
I was facing the same issue and solved it using by http:// or https:// in React.
Like as:
<a target="_blank" href="http://www.example.com/" title="example">See detail</a>
You can use for your dynamic URL:
<Link to={{pathname:`${link}`}}>View</Link>
For V3, although it may work for V4. Going off of Eric's answer, I needed to do a little more, like handle local development where 'http' is not present on the URL. I'm also redirecting to another application on the same server.
Added to the router file:
import RedirectOnServer from './components/RedirectOnServer';
<Route path="/somelocalpath"
component={RedirectOnServer}
target="/someexternaltargetstring like cnn.com"
/>
And the Component:
import React, { Component } from "react";
export class RedirectOnServer extends Component {
constructor(props) {
super();
// If the prefix is http or https, we add nothing
let prefix = window.location.host.startsWith("http") ? "" : "http://";
// Using host here, as I'm redirecting to another location on the same host
this.target = prefix + window.location.host + props.route.target;
}
componentDidMount() {
window.location.replace(this.target);
}
render(){
return (
<div>
<br />
<span>Redirecting to {this.target}</span>
</div>
);
}
}
export default RedirectOnServer;
I am offering an answer relevant to React Router v6 to handle dynamic routing.
I created a generic component called redirect:
export default function Redirect(params) {
window.location.replace('<Destination URL>' + "/." params.destination);
return (
<div />
)
}
I then called it in my router file:
<Route path='/wheretogo' element={<Redirect destination="wheretogo"/>}/>
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
function App() {
return (
<Router>
<Route path="/" exact>
{window.location.replace("http://agrosys.in")}
</Route>
</Router>
);
}
export default App;
Using React with TypeScript, you get an error as the function must return a React element, not void. So I did it this way using the Route render method (and using React router v4):
redirectToHomePage = (): null => {
window.location.reload();
return null;
};
<Route exact path={'/'} render={this.redirectToHomePage} />
Where you could instead also use window.location.assign(), window.location.replace(), etc.
Complementing Víctor Daniel's answer here: Link's pathname will actually take you to an external link only when there's the 'https://' or 'http://' before the link.
You can do the following:
<Link to={{ pathname:
> "https://example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies"
> }} target="_blank" />
Or if your URL doesn't come with 'https://', I'd do something like:
<Link to={{pathname:`https://${link}`}} target="_blank" />
Otherwise it will prepend the current base path, as Lorenzo Demattécommented.
If you are using server-side rending, you can use StaticRouter. With your context as props and then adding <Redirect path="/somewhere" /> component in your app. The idea is every time React Router matches a redirect component it will add something into the context you passed into the static router to let you know your path matches a redirect component.
Now that you know you hit a redirect you just need to check if that’s the redirect you are looking for. then just redirect through the server. ctx.redirect('https://example/com').
You can now link to an external site using React Link by providing an object to to with the pathname key:
<Link to={ { pathname: '//example.zendesk.com/hc/en-us/articles/123456789-Privacy-Policies' } } >
If you find that you need to use JavaScript to generate the link in a callback, you can use window.location.replace() or window.location.assign().
Over using window.location.replace(), as other good answers suggest, try using window.location.assign().
window.location.replace() will replace the location history without preserving the current page.
window.location.assign() will transition to the URL specified, but will save the previous page in the browser history, allowing proper back-button functionality.
location.replace()
location.assign()
Also, if you are using a window.location = url method as mentioned in other answers, I highly suggest switching to window.location.href = url.
There is a heavy argument about it, where many users seem to adamantly want to revert the newer object type window.location to its original implementation as string merely because they can (and they egregiously attack anyone who says otherwise), but you could theoretically interrupt other library functionality accessing the window.location object.
Check out this conversation. It's terrible.
JavaScript: Setting location.href versus location
I was able to achieve a redirect in react-router-dom using the following
<Route exact path="/" component={() => <Redirect to={{ pathname: '/YourRoute' }} />} />
For my case, I was looking for a way to redirect users whenever they visit the root URL http://myapp.com to somewhere else within the app http://myapp.com/newplace. so the above helped.

Custom Teams theming with fluentui/-react-northstar

Docs for fluentui-react-northstar theming can be found here.
I am struggling to understand how to make use of a custom theme. It seems I need one provider for the Teams base theme, and then a nested one for my own theme (which may well be wrong)
<Provider theme={teamsTheme}>
<Provider theme={myTheme}>
But I only want to make a few changes to the base theme, for example making the brand colour red. The documentation doesn't really explain how to make use of it.
For example, the docs show this:
const theme = {
siteVariables: {
brand: 'darkred',
...
but this does nothing to change the primary colour in the app...
Any pointers would be greatly appreciated.
NOTE: I originally went to post this question on the github page, but it said questions should be asked here
After a bit of trial and error, I found that this works:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { Button, Provider, mergeThemes, teamsTheme } from '#fluentui/react-northstar';
const customTheme = {
siteVariables: {
colorScheme: {
brand: {
'background': 'darkred',
}
}
}
}
ReactDOM.render(
<Provider theme={mergeThemes(teamsTheme, customTheme)}>
<Button primary content="Hello" />
</Provider>
,
document.getElementById('app'),
)
Gives this:

Material-UI Theme : How to manage order or priority the .MuiXXX classes are applied?

[EDIT April 19th]
I have created a CODESANDBOX to show the problem, of course, that doesn't occur in sandbox.
The only difference between this code and mine is that I have duplicated the code of the Button component in the SANDBOX example, whereas in my App the Button component is imported from a library (that belongs to the same yarn workspace as the app). The library is built with webpack and babel, excluding React and Material-UI
externals: {
react: "react",
"react-dom": "react-dom",
"react-router": "react-router",
"react-router-dom": "react-router-dom",
"#material-ui/core": "#material-ui/core",
"#material-ui/icons": "#material-ui/icons",
"#material-ui/lab": "#material-ui/lab",
"#material-ui/styles": "#material-ui/styles",
formik: "formik",
},
Inspecting the components in the Browser shows the difference when styling, between sandbox and my app :
on both sides, the class are applied to the component the same way:
in sandbox
in my app
but on sandBox, the MuiButtonBase-root background-color is overridden by the MuiButton-root background-color
whereas it is the opposite in my app. The MuiButton-root backGroundColor seems to be overriden bu the MuiButtonBase-root background-color
However, if I create a component RecreatedButton in the App by just importing the Button component of my UI Library, and re-exported it without changing anything (just passing a specific props the component is requested), then the styling is applied correctly, as in the sandbox example.
this is kind of weird to me...
Why such a behavior ?
just importing and rexporting as is the component
import {
Button as LibraryButton,
EButtonTypes,
IButtonProps,
} from "#mylibrairy/reactcomponentscommon"; <---- importing the button
import React from "react";
const RecreatedButton: React.FC<IButtonProps> = (
props: IButtonProps
): JSX.Element => {
return (
<LibraryButton type={EButtonTypes.BUTTON}>
{props.children}
</LibraryButton>
);
};
export { RecreatedButton };
Using both in app.ts. One got the theme, the other not
import { ThemeProvider } from "#material-ui/core/styles";
import {
Button as LibraryButton,
EButtonTypes,
IButtonProps,
} from "#mylibrairy/reactcomponentscommon"
import React from "react";
import AppBar from "../../UIComponents/AppBar";
import { RecreatedButton } from "../../UIComponents/Button";
import { MUITheme } from "./../../Theming/defaultTheme";
export const MainApp: React.FC = (): JSX.Element => {
return (
<ThemeProvider theme={MUITheme}>
<>
<AppBar />
<LibraryButton type={EButtonTypes.BUTTON}> I'm the library component, directly used as is, and background color is NOT CORRECT ></LibraryButton>
<RecreatedButton>
I'm recreated button, just rexporting the library component, and the backgroundcolor is correct !?!?{" "}
</RecreatedButton>
</>
</ThemeProvider>
);
};
finally I found one solution (not sure that it fixes the root cause as I still do not understand where it comes from).
I Guess it may helps some people here that are facing a similar issue with global theming in Material-Ui.
It turned out that I had to change the way to build my react/material-Ui components library #mylibrairy/reactcomponentscommon.
1- Make sure that in the library, all imports where such as import { Button} from "#material-ui/core" and not for example import Button from "#material-ui/core/Button"
2- Remove the usage of file-loader plugin in the .babelrc to make sure it doesn't change the way to import material-ui components
3- Push #material-ui/core and #material-ui/icons as a dev and peer dependencies in the package.json of the library.
4- Rebuilt the library using webpack and babel to compile typescript tsx to js.
All issues of priority seems to disappear (have done a lot of tests and checked in the chrome dev tools). In the example above, the .MuiButton-root class is well applied after the .MuiButtonBase-root one, thus overriding as expected the backgroundColor.
Would admit that I'm a little bit confused why this fixed the issue...
Rgds
For me, i just had to import "makeStyles" and "createStyles" from "#material-ui/core" not from "#material-ui/core/styles". i just did this and it fixed the issue but took me a lot of time to figure this out.
so import them like this:
import { makeStyles, createStyles } from "#material-ui/core";
not like this:
import { makeStyles, createStyles } from "#material-ui/core/styles";
You may try overriding default globals for MuiButtonBase
const theme = createMuiTheme({
props: {
// Name of the component ⚛️
MuiButtonBase: {
// The default props to change
root:{
backgroundColor: 'red'
}
},
},
});
function DefaultProps() {
return (
<ThemeProvider theme={theme}>
<Button>Change default props</Button>
</ThemeProvider>
);
}
Working sandbox here - https://codesandbox.io/s/override-button-base-7qwd5

React + Material-UI - Warning: Prop className did not match

I'm having difficulty with differences between client-side and server-side rendering of styles in Material-UI components due to classNames being assigned differently.
The classNames are assigned correctly on first loading the page, but after refreshing the page, the classNames no longer match so the component loses its styling. This is the error message I am receiving on the Console:
Warning: Prop className did not match.
Server: "MuiFormControl-root-3 MuiFormControl-marginNormal-4
SearchBar-textField-31"
Client: "MuiFormControl-root-3 MuiFormControl-marginNormal-4
SearchBar-textField-2"
I've followed the Material-UI TextField example docs, and their accompanying Code Sandbox example, but I can't seem to figure out what is causing the difference between the server and client classNames.
I experienced a similar issue when adding Material-UI Chips with a delete 'x' icon. The 'x' icon rendered with a monstrous 1024px width after refreshing. The same underlying issue being that icon was not receiving the correct class for styling.
There are a few questions on Stack Overflow addressing why the client and server might render classNames differently (e.g. need to upgrade to #Material-UI/core version ^1.0.0, using a custom server.js, and using Math.random in setState), but none of these apply in my case.
I don't know enough to tell whether this Github discussion might help, but likely not since they were using a beta version of Material-UI.
Minimal steps to reproduce:
Create project folder and start Node server:
mkdir app
cd app
npm init -y
npm install react react-dom next #material-ui/core
npm run dev
edit package.json:
Add to 'scripts': "dev": "next",
app/pages/index.jsx:
import Head from "next/head"
import CssBaseline from "#material-ui/core/CssBaseline"
import SearchBar from "../components/SearchBar"
const Index = () => (
<React.Fragment>
<Head>
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charSet="utf-8" />
</Head>
<CssBaseline />
<SearchBar />
</React.Fragment>
)
export default Index
app/components/SearchBar.jsx:
import PropTypes from "prop-types"
import { withStyles } from "#material-ui/core/styles"
import TextField from "#material-ui/core/TextField"
const styles = (theme) => ({
container: {
display: "flex",
flexWrap: "wrap",
},
textField: {
margin: theme.spacing.unit / 2,
width: 200,
border: "2px solid red",
},
})
class SearchBar extends React.Component {
constructor(props) {
super(props)
this.state = { value: "" }
this.handleChange = this.handleChange.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}
handleChange(event) {
this.setState({ value: event.target.value })
}
handleSubmit(event) {
event.preventDefault()
}
render() {
const { classes } = this.props
return (
<form
className={classes.container}
noValidate
autoComplete="off"
onSubmit={this.handleSubmit}
>
<TextField
id="search"
label="Search"
type="search"
placeholder="Search..."
className={classes.textField}
value={this.state.value}
onChange={this.handleChange}
margin="normal"
/>
</form>
)
}
}
SearchBar.propTypes = {
classes: PropTypes.object.isRequired,
}
export default withStyles(styles)(SearchBar)
Visit page in browser localhost:3000 and see this:
red border around TextField component
Refresh the browser and see this:
TextField component's styles are gone
Notice that the red border around TextField disappears.
Relevant Libs:
"react": 16.4.0
"react-dom": 16.4.0
"next": 6.0.3
"#material-ui/core": 1.2.0
The problem is the SSR rendering in Next.js, which produces the style fragment before the page is rendered.
Using Material UI and Next.js (as the author is using), adding a file called _document.js solved the problem.
Adjusted _document.js (as suggested here):
import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheets } from '#material-ui/styles'; // works with #material-ui/core/styles, if you prefer to use it.
import theme from '../src/theme'; // Adjust here as well
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
{/* Not exactly required, but this is the PWA primary color */}
<meta name="theme-color" content={theme.palette.primary.main} />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with server-side generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
// Resolution order
//
// On the server:
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. document.getInitialProps
// 4. app.render
// 5. page.render
// 6. document.render
//
// On the server with error:
// 1. document.getInitialProps
// 2. app.render
// 3. page.render
// 4. document.render
//
// On the client
// 1. app.getInitialProps
// 2. page.getInitialProps
// 3. app.render
// 4. page.render
// Render app and page and get the context of the page with collected side effects.
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 fragment is rendered after the app and page rendering finish.
styles: [...React.Children.toArray(initialProps.styles), sheets.getStyleElement()],
};
};
This problem is related to MUI using dynamic class name which contain an ID. The IDs from the server side rendered CSS are not the same as the client side CSS, hence the mismatch error. A good start is to read the MUI SSR documentation
If you have this problem with nextjs (as I did) follow the example provided by the MUI team, which can be found here: material-ui/examples/nextjs
The most important part is in "examples/nextjs/pages/_app.js":
componentDidMount() {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side');
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles);
}
}
the related ticket can be found here: mui-org/material-ui/issues/15073
what it does, is remove the server side rendered stylesheet and replace it by a new client side rendered one
The issue is the server side generates the class names but style sheets are not automatically included in the HTML. You need to explicitly extract the CSS and append it to the UI for the server side rendered components. The whole process is explained here: https://material-ui.com/guides/server-rendering/
There is one other important, separate issue here: Material UI V4 is not React Strict Mode compatible. Strict mode compatibility is slated for version 5 with the adoption of the Emotion style engine.
Until then, be sure you disable React Strict Mode. If you're using Next.js, this is turned on by default if you've created your app using create-next-app.
// next.config.js
module.exports = {
reactStrictMode: false, // or remove this line completely
}
I had the same problem with Next.js and styled component, with the transpilation by Babel. Actually, the class names are different on the client and the server side.
Fix it in writing this in your .babelrc :
{
"presets": ["next/babel"],
"plugins": [
[
"styled-components",
{ "ssr": true, "displayName": true, "preprocess": false }
]
]
}
I met this problem on Material-ui V5. The solution to fix this problem is to make sure that class name generator needs to behave identically on the server and on the client.
so adding the code below in your _app.js:
import { StylesProvider, createGenerateClassName } from '#mui/styles';
const generateClassName = createGenerateClassName({
productionPrefix: 'c',
});
export default function MyApp(props) {
return <StylesProvider generateClassName={generateClassName}>...</StylesProvider>;
}
// 1 . Warning: prop classname did not match. Material ui with React Next.js
// 2 . Use your customization css here
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
title: {
flexGrow: 1,
},
my_examle_classssss: {
with: "100%"
}
}));
// 3 . Here my Component
const My_Example_Function = () => {
const classes = useStyles();
return (
<div className={classes.root}>
<Container>
<Examle_Component> {/* !!! Examle_Component --> MuiExamle_Component*/}
</Examle_Component>
</Container>
</div>
);
}
export default My_Example_Function
// 4. Add name parameter to the makeStyles function
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
},
title: {
flexGrow: 1,
},
my_examle_classssss: {
with: "100%"
},
}), { name: "MuiExamle_ComponentiAppBar" });
{/* this is the parameter you need to add { name: "MuiExamle_ComponentiAppBar" } */ }
{/* The problem will probably be resolved if the name parameter matches the first className in the Warning: you recive..
EXAMPLE :
Warning: Prop `className` did not match.
Server: "MuiSvgIcon-root makeStyles-root-98"
Client: "MuiSvgIcon-root makeStyles-root-1"
The name parameter will be like this { name: "MuiSvgIcon" }
*/ }
I like to share this mismatching case:
next-dev.js?3515:32 Warning: Prop className did not match. Server:
"MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-12
MuiSwitch-switchBase MuiSwitch-colorSecondary" Client:
"MuiButtonBase-root MuiIconButton-root PrivateSwitchBase-root-12
MuiSwitch-switchBase MuiSwitch-colorSecondary
PrivateSwitchBase-checked-13 Mui-checked"
On client there are two more classes which means that the behavior on client-side is different. In this case, this component shouldn't render on server-side. The solution is to dynamically render this component:
export default dynamic(() => Promise.resolve(TheComponent), { ssr: false });
I had a problem with different classNames for client and server. I was using React, Material-UI, makeStyles and SSR (server-side rendering).
The error was:
Warning: Prop `className` did not match. Server: "jss3" Client: "App-colNav-3"
I spent several hours before I figured out that I had discrepancy in webpack mode for client and server. The scripts in package.json were:
"devServer": "webpack --config webpack.server.config.js --mode=production --watch",
"devClient": "webpack --mode=development --watch",
After I changed both to have development mode, the problem was solved :)
"devServer": "webpack --config webpack.server.config.js --mode=development --watch",
"devClient": "webpack --mode=development --watch",
If somebody is still struggling even after trying above solutions, Try this
If you have used noSsr prop in any of your components or theme, then remove it.
I had the following config in mui theme object, which was causing this problem.
import { createTheme, responsiveFontSizes } from "#mui/material/styles";
let theme = createTheme({
components: {
MuiUseMediaQuery: {
defaultProps: {
noSsr: true,
},
},
},
palette: {
mode: "light",
common: {
black: "#000",
white: "#fff",
},
primary: {
main: "#131921",
contrastText: "#fff",
},
secondary: {
main: "#fb6a02",
contrastText: "#fff",
}
}
})
RemovingnoSSr fixed all of the issues in my app including style mismatch between client and server.
The problem is cause by Nextjs server side rendering. In order to solve I do as following:
Make a component to detect whether is it from Client side
import { useState, useEffect } from "react";
interface ClientOnlyProps {}
// #ts-ignore
const ClientOnly = ({ children }) => {
const [mounted, setMounted] = useState<boolean>(false);
useEffect(() => {
setMounted(true);
}, []);
return mounted ? children : null;
};
export default ClientOnly;
Wrap my page component using ClientOnly component
export default function App() {
return (
<ClientOnly>
<MyOwnPageComponent>
</ClientOnly>
);
}
So the idea is, if it is client side then only render the component on the page. Therefore if current rendering is from Client side, render <MyOwnPageComponent>, else render nothing
In my case the issue happened because of different compilation modes of webpack for client-side code and server-side: client's bundle was generated by webpack using "production" mode, while server ran some SSR code from a package optimized for "development". This created a different "className" hash in styled-components in generateAndInjectStyles():
if (process.env.NODE_ENV !== 'production') dynamicHash = phash(dynamicHash, partRule + i);
So my fix was just to align the webpack modes.
You can add the name in anywhere you use makeStyles, like this:
const useStyles = makeStyles({
card: {
backgroundColor: "#f7f7f7",
width: "33%",
},
title: {
color: "#0ab5db",
fontWeight: "bold",
},
description: {
fontSize: "1em"
}
}, { name: "MuiExample_Component" });
I am not sure how it works, but I found it here: Warning: Prop `className` did not match ~ Material UI css arbitrarily breaks on reload
I'm also using NextJS + MUI v5 and I ran into this exact error right after merging Git branches. I suspect the merge corrupted something in the cache. I deleted the contents of .next/ and restarted the dev server and the error went away.
#Leonel Sanches da Silva's answer didn't work for me, as #material-ui/styles is deprecated, but using a snippet I found for another (non-material UI) project seems to have worked just fine for me:
Hat tip to Raul Sanchez on dev.to for the answer to this one.
Next doesn't fetch styled-components styles on the server, to do that you need to add this page to 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 code may update, so check Next's styled-components example for the latest.

React-Router Router.HistoryLocation refreshes the page

I am working on React-Express-Node application and focussing on SPA. I am using the react-router.
My server.js file looks like this (only routing part):
app.use(function(req, res, next) {
Router.run(routes,function(Handler, state) {
var ele = React.createElement(Handler);
res.render(path.join(__dirname + '/public/index'), {html: html});
});
next();
});
And the routes file has this code (pasting the main part):
module.exports = (
<Route name="app" path="/" handler={Main}>
<Route name="about" path="about" handler={About}/>
<Route name="about/id" path="about/:id" handler={About}/>
<DefaultRoute name="default" handler={Home} />
</Route>
);
And client.js looks like this:
Router.run(routes, function(Root,state){
React.render(<Root />,document.getElementById('app'));
});
This setup works fine without any problem.
Now, I want to use the History API pushstate so that i can have better urls and get rid of #. To do that, I added Router.HistoryLocation as the second parameter in client.js and it works, it removes the # and gives clean urls. But however, my page refreshes which I don't want.
I have searched this all over and found couple of solutions but they are either using Flux or a custom router. I am surely missing something related to state but not able to figure out. Can someone point me to the right direction ?
It is entirely possible to use Router.HistoryLocation with express server rendered SPA app (I'm doing it now).
You need to tell the server.js file where to get the history, i.e. req.url... like this.
Router.run(routes, req.url, function(Handler, state) {
var ele = React.createElement(Handler);
res.render(path.join(__dirname + '/public/index'), {html: html});
});
If the server is set up correctly, the next item to check is how you're transitioning to the routes. Using a traditional anchor <a/> tag will always refresh the page. React Router has provided their own <Link/> component that allows you to navigate to your routes without causing a page reload. You can also look into calling transitionTo() directly on your router to cause a navigation as well.
Also, when a <Link/> tag is active it will also pass an active class to the tag so css can style it accordingly.
Link
<Link to="route" props={} query={}>My Link</Link>
Navigation
router.transitionTo('route', params, query);
Check out the Link Docs and Navigation Docs.
This might help Meteor users:
import React, {Component} from 'react';
import {Link} from 'react-router';
export default class MenuItem extends Component{
render(){
return(
<li>
<Link to={this.props.link}>{this.props.page}</Link>
</li>
);
}
}
Import Link from react-router and use <Link /> to create your links will prevent the page from refreshing.
this.props.link and this.props.page are your dynamic data.

Resources