I use react router for routing in my create-react-app. All routes work as expected using "npm start" ("react-scripts start").
However, now I would like to deploy my application and therefore ran "npm run-scripts build" ("react-scripts build"). Afterwards I ran "serve -s build" in order to start the webserver. In this version of my web application the static routes are working but the dynamic ones don't.
An example of a static route configuration looks like this. This route works in both, the dev mode and the build mode of the react application. Example URL: "http://localhost:5000/dashboard/viewData".
{
path: "/dashboard/viewData/",
component: withAuth(CrosstabTest),
exact: false
}
The following route is dynamic and doesn't work. However, my guess is that it is not due to ":processFlowItemId" but instead due to additional URL parameters. Example URL: "http://localhost:5000/dashboard/definition/1?id=0744a761-111c-446c-9bb5-2763c5c8bb04".
{
path: "/dashboard/definition/:processFlowItemId",
component: withAuth(DefinitionContainer),
exact: false
}
When I run the example URL without "?id=0744a761-111c-446c-9bb5-2763c5c8bb04" the application loads the component but as the id parameter is missing, I get an error. But at least it's an indication for the fact that the route is working in general but has issues with the parameter. When opening the complete example URL including the id field, the component seems not to be loaded at all (meaning react-router doesn't recognize the URL as an instance of the pattern defined in the route.
As mentioned, the issue only occurs when using the build version of the app.
Edit:
Below I describe the code in more details:
I have a config with my routes:
export const mainRoutes: any = [
{
path: "/dashboard/",
component: Dashboard,
exact: false
},
]
export const dashboardRoutes: any = [
{
path: "/dashboard/viewData/",
component: withAuth(CrosstabTest),
exact: false
},
{
path: "/dashboard/definition/:processFlowItemId",
component: withAuth(DefinitionContainer),
exact: false
},
]
The main route ("mainRoute") is rendered here:
import { mainRoutes } from './routes/routes'
import { Switch, BrowserRouter, Route } from 'react-router-dom'
class App extends React.Component<any> {
public render() {
return (
<BrowserRouter>
{mainRoutes.map((route: any, i: number) => (
<Switch key={i}>
<Route key={i} path={route.path} component={route.component} exact={route.exact} />
</Switch>
))
}
</BrowserRouter>
)
}
}
export default App
In the Dashboard component ("Dashboard") I render a subroute:
import { Switch, Route } from 'react-router-dom'
import { dashboardRoutes } from '../../routes/routes'
...
dashboardRoutes.map((route: any, i: number) => (
<Switch key={i}>
< Route exact={true} key={i} path={route.path} render={(props) => {
return <route.component key={i} {...props} />
}} />
</Switch>
))
...
I had the same problem and solved it by exchanging BrowserRouter for HashRouter
Related
Starting my app on --> http://localhost:8000
I created a component called "NavBar", and in this component I created a Link to "/home".
In "/home" Im just rendering the following text --> "Test React Router Dom"
On "NavBar" component, clicking on Link to "/home" it work, but when I try to refresh the page on "/home" or just typing URL: "http://localhost:8000/home" it fails.
"react": "^18.2.0"
"react-dom": "^18.2.0"
"react-router-dom": "^6.4.1"
"typescript": "^4.8.4",
Error -->
// App.tsx
import { NavBar } from './components/NavBar'
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
export const App = () => {
return (
<div>
<Router>
<Routes>
<Route path="/" element={<NavBar />} />
<Route path="/home" element={<h1>Test React Router Dom</h1>} />
</Routes>
</Router>
</div>
)
}
// NavBar.tsx
import './navBar.style.scss'
import { Link } from 'react-router-dom'
export const NavBar = () => {
return (
<>
<Link to={'/home'}>Link to Home</Link>
</>
)
}
//index.tsx
import { createRoot } from 'react-dom/client'
import { App } from './App'
const container = document.getElementById('root') as HTMLElement
const root = createRoot(container)
root.render(<App />)
This will be because your server needs to support HTML5 push state. When you navigate by clicking a link that's using the HTML5 history API (which react-router uses under the hood), its only changing the URL in the address bar -- it doesnt actually try to fetch that page from the server. However when you refresh or hit the URL from the outside of the site, the browser will simply try to load that URL from the server -- there is no client side code even loaded to use the history API.
Your server needs a wildcard route configured such that any and all paths serve your index.html. Then, your JS etc will load, read from the URL, and display the desired page.
If it's an express server you'd need something like this -- but the exact details really depend on your server technology and setup:
app.get('*', function(request, response) {
response.sendfile(__dirname + '/public/index.html');
});
I found a solution, Im using WebPack and I added the following config on Development mode -->
devServer: {
historyApiFallback: true,
}
But I don't understand how "historyApiFallback" works.
Will I need to add some another configuration on my Production mode to prevent the same error?
// webpack.prod.js
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
const TerserPlugin = require('terser-webpack-plugin')
module.exports = {
mode: 'production',
devtool: 'source-map',
devServer: {
historyApiFallback: true,
},
optimization: {
minimizer: [new CssMinimizerPlugin(), new TerserPlugin()],
},
plugins: [],
}
Is it not possible to route to the same component with a wildcard path?
If in React I have something like:
<Router>
<Switch>
<Route path="/path/:id" children={<Component />} />
</Switch>
</Router>
all the requests:
/path/123
/path/123/p
/path/123/p/1
will route to the same /path/123
How can I tell Gatsby to do the same?
createPage({
path: `/path/123/*`,
component,
context
})
Or what is the solution to this problem, a redirect engine of some sorts?
I think you are looking for client-only routes. Given a page (or template if it's created from gatsby-node.js) you can:
import React from "react"
import { Router } from "#reach/router"
import Layout from "../components/Layout"
import SomeComponent from "../components/SomeComponent"
const App = () => {
return (
<Layout>
<Router basepath="/app">
<SomeComponent path="/path" />
</Router>
</Layout>
)
}
export default App
Note: assuming a src/pages/app/[...].js page (File System Route API structure).
When a page loads, Reach Router looks at the path prop of each component nested under <Router />, and chooses one to render that best matches window.location (you can learn more about how routing works from the #reach/router documentation).
Alternatively, you can use an automated approach (plugin: gatsby-plugin-create-client-paths) by:
{
resolve: `gatsby-plugin-create-client-paths`,
options: { prefixes: [`/path/*`] },
},
Which will validate all routes under /path.
Or for a more customizable approach, in your gatsby-node.js:
exports.onCreatePage = async ({ page, actions }) => {
const { createPage } = actions
// page.matchPath is a special key that's used for matching pages
// only on the client.
if (page.path.match(/^\/path/)) {
page.matchPath = "/path/*"
// Update the page.
createPage(page)
}
}
Disclaimer: These routes will exist on the client only and will not correspond to index.html files in an app’s built assets. If you’d like site users to be able to visit client routes directly, you’ll need to set up your server to handle those routes appropriately.
How to change base url in create-react-app? So that the application does not open with localhost:3000/, but localhost:3000/myurl?
You will need to do a lot of adjusting if you want react to launch in a subdir. like changing path of assets, components etc. This Guide is a good starting point
on how to accomplish your needs. Again Its not recommended, if you want like to switch to /myUrl as soons as it launches, you could do a componentDidMount() and force it there.
Assuming you are using react-router, you can achieve that by using a Redirect to navigate your app from / to /myurl.
First, declare a route config like this:
// routers.js
import React from "react";
import { Redirect } from "react-router-dom";
export const redirectRoutes = [
{
path: "/",
exact: true,
// handle redirect case: / -> /myurl/
render: () => <Redirect to={`/myurl`} />
}
]
Then, inside your App.js:
import React from "react";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import { redirectRoutes } from "./routers.js";
// generate app reidrect routes
const redirectRouteComponents = redirectRoutes.map(route => (
<Route
key={route.path}
path={route.path}
exact={route.exact}
render={route.render}
/>
));
const App = () => <Router>{redirectRouteComponents}</Router>;
I have multiple apps as part of one React-redux-typescript project. All of these individual apps are part of a core-application. I want to dynamically set up routing.
My current routes look like this:
Routes.tsx
import HomePageApp from "../Components/Home/HomeApp";
import TestApp from "../Components/Test/TestApp";
export default function Routes() {
return (
<Switch>
<RedirectIfAuthenticated
exact={true}
isAuthenticated={true}
path={Path.homePath} --> "/"
component={HomePage} ---> AppName coming from import statement on top
redirectPath={Path.homePath} --> "/"
/>
<RedirectIfAuthenticated
isAuthenticated={true}
path={Path.apps.test} --> "/test"
component={TestApp} --> AppName from import on top
redirectPath={Path.homePath} --> "/"
/>
</Switch>
);
}
And RedirectIfAuthenticated simply redirects to correct applications' landing pages.
RedirectIfAuthenticated.tsx
export default function RedirectIfAuthenticated({
component,
redirectPath,
isAuthenticated,
...rest
}: IRedirectIfAuthenticatedProps) {
const Component = component;
const render = (renderProps: RouteComponentProps<any>) => {
let element = <Component {...renderProps} />;
return element;
};
return <Route {...rest} render={render}/>;
}
I've a config file like this:
Manifest.ts
export let manifest = {
apps: [
{
componentPath: "/Test/App",
path: "/test",
name: "Test"
},
...more objects for other apps
]
};
In my Routes.tsx, I want to make use of my manifest to render the RedirectIfAuthenticated component.
so I can figure out this change:
for brevity showing the dirty approach but the actual code iterates over the manifest using .map and renders RedirectIfAutenticated.
const app = manifest.apps.find(app => app.name === "Test");
<Switch>
<RedirectIfAuthenticated
isAuthenticated={true}
path={app.path} --> "/test"
component={What should I do here? How to pass component reference by path??}
redirectPath={"/"} ==> I typically get this from my manifest..
/>
</Switch>
One option is to do this:
component={require("path from manifest").default}
but our tslint throws a bunch of errors at this. Other than this I can't figure out how to pass the component reference here dynamically. Looking for a better approach.
The Routes.tsx needs to be dynamic so that adding new apps is a matter of configuration so I can't do imports on top because I dont know what's gonna be added in config. Thanks.
I was able to use dynamic imports to achieve this. I used this article to understand a few concepts.
private loadComponentFromPath(path: string) {
import(`../../ScriptsApp/${path}`).then(component =>
this.setState({
component: component.default
})
);
}
One important distinction here is if I don't give the path from the root of the app, I get an error saying "Unable to find the module". This is why I've given the full path from the root.
const routes = [
{ component: Root,
routes: [
{ path: '/',
exact: true,
component: Home
},
{ path: '/child/:id',
component: Child,
routes: [
{ path: '/child/:id/grand-child',
component: GrandChild
}
]
}
]
}
]
routes.config.js export routes object using in renderRoutes method which is exported by react-router-config
when I start the app and access the url localhost:3000 the Home component will be rendered .
If access localhost:3000/child the request is 404.
If access localhost:3000/#/child but it render Home component instead of Child component
In the development mode I know can add devServer:{historyApiFallback:true}, but should I do in the production mode ?
If using HashRouter it works well.
So how can I using the BrowserRouter?
Try this:
Step 1: Wrap your <App /> inside <BrowserRouter />
Step 2: <Router path="/child" component={Child} />
Step 3: Point this router using <Link to="/CHILD>Child</Link>"