How to change default landing page to login instead of index - next.js

I am pretty new to next.js and kind of stuck on basic issue.
here is my _app.js
class MyApp extends App {
constructor (props) {
super(props)
this.persistor = persistStore(props.store)
}
static async getInitialProps ({ Component, ctx }) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps({ ctx })
}
return { pageProps }
}
render () {
const { Component, pageProps, store } = this.props
return (
<Provider store={store}>
<PersistGate
loading={null}
persistor={this.persistor}
>
<Component {...pageProps} />
</PersistGate>
</Provider>
)
}
}
export default withRedux(createStore)(withReduxSaga(MyApp))
This is my index.js
function Home (props) {
return (
<div>
<div className='home'>
<Link href='/login'>
<a>Go to Login</a>
</Link>
<h1>THIS IS HOME</h1>
</div>
}
Currently when I start an app it lands on index page.
How do I make this land on Login page directly?
Let me know if you need to see other files?

not sure if this is the right way of doing things but I was able to navigate user on Login page instead of index page when website loads.
// redirect user to login page
useEffect(() => {
Router.push('/login')
})
return (
<div>
</div>
)

Related

Is there a better and cleaner method other than getInitialProps

I have a nextJS site that has a custom _app.js file with the following code.
I have dynamic content from a cms that i use getServerSide props from for each of the pages I have.
const MyApp = ({ Component, pageProps }) => {
const { global } = pageProps
if (global == null) {
return <ErrorPage statusCode={404} />
}
if(global.attributes === undefined){
return <Component />
}
const { metadata, favicon, metaTitleSuffix } = global.attributes
return (
<>
{/* Favicon */}
<Head>
<link
rel="shortcut icon"
href={getStrapiMedia(favicon.data.attributes.url)}
/>
</Head>
{/* Global site metadata */}
<DefaultSeo
titleTemplate={`%s | ${metaTitleSuffix}`}
title="Page"
description={metadata.metaDescription}
/>
{/* Display the content */}
<Component {...pageProps} />
</>
)
}
MyApp.getInitialProps = async (appContext) => {
const appProps = await App.getInitialProps(appContext)
const globalLocale = await getGlobalData(appContext.router.locale)
return {
...appProps,
pageProps: {
global: globalLocale,
},
}
}
export default MyApp
Two questions in a way.
A. Do I even need this getInitialProps?
B. Is there a better way of using getInitialProps
Let me know if anymore code is needed

Apply Generec Typescript for retrieving data by using api link and interface as parameter

Goal:
For React TS.
The page List1 and List2 should use the same method named useFetch (retrieve data by using api link) by using generic approach by sending the interface (named Client and TheComputer) to the useFetch.
Each interface has different datamember.
You should enable to use useFetch by many and different interface's name.
In other words,
UseFetch should be a independent tool that can be used by different interface by sending api link and interface asa parameter.
Problem:
You are enable to use react js to achieve it (without using syntax interface) but not for React TS.
I have problem to make useFetch as a independent component with react TS. How should it be solved?
Other info:
*It is achieved for ReactJS but not for ReactTS.
*Somehow it doesn't work in my local computer probably due to strictly linting and TS error.
You need to use interface to order to retrieve data and then display it.
*Newbie in ReactTS
Thank you!
Stackblitz:
JS
https://stackblitz.com/edit/react-mjvs38?
TS
https://stackblitz.com/edit/react-ts-7oeqen?
index.tsx
import React, { Component } from 'react';
import { render } from 'react-dom';
import {
BrowserRouter as Router,
Link,
Route,
Routes,
useParams,
} from 'react-router-dom';
import './style.css';
import useFetch1 from './useFetchTS1';
import useFetch2 from './useFetchTS2';
function App() {
return (
<div>
<h1>Home</h1>
</div>
);
}
function List1() {
const { data, loading, error } = useFetch1('https://api.github.com/users');
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<img src={item.avatar_url} />
<div>{item.id}</div>
</div>
))}
;
</div>
);
}
function List2() {
const { data, loading, error } = useFetch2(
'https://jsonplaceholder.typicode.com/todos'
);
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<div>
Id: {item.id} Title: {item.title}
</div>
</div>
))}
;
</div>
);
}
render(
<Router>
<div>
<header>
<Link to="/">Home</Link>
<br />
<Link to="/list1">List1</Link>
<br />
<Link to="/list2">List2</Link>
<br />
<hr />
</header>
<Routes>
<Route path="/" element={<App />} exact></Route>
<Route path="/list1" element={<List1 />} exact></Route>
<Route path="/list2" element={<List2 />}></Route>
</Routes>
</div>
</Router>,
document.getElementById('root')
);
useFetchTS1.tsx
import { useState, useEffect } from 'react';
interface Client {
id: number;
avatar_url: string;
}
export default function useFetch1(url) {
const [data, setData] = useState<Client[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
}
useFetchTS2.tsx
import { useState, useEffect } from 'react';
interface TheComputer {
id: number;
title: string;
}
export default function useFetch2(url) {
const [data, setData] = useState<TheComputer[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
}
There is a design that used to be called the Service Agent pattern that may work well for you:
Use React in the standard way, with useEffect etc
Views simply get type safe data and update their model
Views know nothing about APIs and just ask the agent class
The agent class can express the API interface
A lower level fetch class can do plumbing in a shared way
For sonething to compare against, see if any of my code is useful:
View classes
API classes
In these examples:
CompaniesContainer is the view class
ApiFetch sends and receives any type of API payload, and does common tasks such as refreshing OAuth tokens
ApiClient ensures that views use only type safe requests and responses
You can adapt some of this into a React hook if you prefer. Personally though I prefer to limit React syntax to view logic, and use plain Typescript classes in other places. I can then use equivalent classes in other types of UI, such as mobile apps.
So I believe we can get the useFetch hook to be generic for you if we change it to the following:
import { useEffect, useState } from 'react';
export default function useFetch1<TData = any>(url) {
const [data, setData] = useState<TData[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function init() {
//debugger;
try {
const response = await fetch(url);
if (response.ok) {
const json = await response.json();
setData(json);
} else {
throw Response;
}
} catch (e) {
setError(e);
} finally {
setLoading(false);
}
}
init();
}, [url]);
return { data, error, loading };
We use a generic <TData = any> in the function definition and TData[] in the useState hook for data.
Then in your index.tsx file you can define the interfaces there, and pass them to the generic useFetch1 hook like this:
useFetch1<Client>('https://api.github.com/users');
and
useFetch1<TheComputer>('https://jsonplaceholder.typicode.com/todos');
This lets you have the useFetch hook be generic, and still get the data returned to be the correct Interface/Type.
Your updated index.tsx file would look like this:
import './style.css';
import {
Link,
Route,
BrowserRouter as Router,
Routes,
useParams,
} from 'react-router-dom';
import React, { Component } from 'react';
import { render } from 'react-dom';
import useFetch1 from './useFetchTS1';
interface Client {
id: number;
avatar_url: string;
}
interface TheComputer {
id: number;
title: string;
}
function App() {
return (
<div>
<h1>Home</h1>
</div>
);
}
function List1() {
const { data, loading, error } = useFetch1<Client>('https://api.github.com/users');
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<img src={item.avatar_url} />
<div>{item.id}</div>
</div>
))}
;
</div>
);
}
function List2() {
const { data, loading, error } = useFetch1<TheComputer>(
'https://jsonplaceholder.typicode.com/todos'
);
if (loading) {
return <div>Loading</div>;
}
return (
<div>
{data.map((item) => (
<div>
<div>
Id: {item.id} Title: {item.title}
</div>
</div>
))}
;
</div>
);
}
render(
<Router>
<div>
<header>
<Link to="/">Home</Link>
<br />
<Link to="/list1">List1</Link>
<br />
<Link to="/list2">List2</Link>
<br />
<hr />
</header>
<Routes>
<Route path="/" element={<App />} exact></Route>
<Route path="/list1" element={<List1 />} exact></Route>
<Route path="/list2" element={<List2 />}></Route>
</Routes>
</div>
</Router>,
document.getElementById('root')
);
This utilizes generic types: https://www.typescriptlang.org/docs/handbook/2/generics.html
I updated the stackblitz too and seems to be working: https://stackblitz.com/edit/react-ts-zasl3r?file=useFetchTS1.tsx

How to enable ssg/ssr in next.js with custom App component

I have a custom _app.js which looks like this:
function MyApp({ Component, pageProps }) {
const Layout = Component.layoutProps?.Layout || React.Fragment
const layoutProps = Component.layoutProps?.Layout
? { layoutProps: Component.layoutProps }
: {}
const meta = Component.layoutProps?.meta || {}
const description =
meta.metaDescription || meta.description || 'Meta Description'
const store = useStore(pageProps.initialReduxState)
return (
<QueryClientProvider client={queryClient}>
<Provider session={pageProps.session}>
<Title suffix="My Dynamic Site">{meta.metaTitle || meta.title}</Title>
<Description>{description}</Description>
<Meta />
<ReduxProvider store={store}>
<PersistGate persistor={store.__PERSISTOR} loading={null}>
<CartBox />
<Layout {...layoutProps}>
<Component {...pageProps} />
</Layout>
</PersistGate>
</ReduxProvider>
<ReactQueryDevtools initialIsOpen={false} />
</Provider>
</QueryClientProvider>
)
}
MyApp.getInitialProps = async (appContext) => {
// calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(appContext);
return { ...appProps }
}
export default MyApp
Now, I would like to fetch data using ssg/ssr data fetching method to help SEO team for my page components.
But, it seems any of the methods aren't working as expected, none of them actually passing props to the page component.
Here's my page component.
const HomePage = ({ title, stars }) => {
console.log(title, stars); // undefined, undefined
return (
<div>
<Header title={title} />
<GhStars stars={stars} />
<Footer />
</div>
)
}
export const getStaticProps = async () => {
return {
props: {
title: "My Dynamic Title From getStaticProps"
}
}
}
// I tried both getInitialProps & getStaticProps independently.
HomePage.getInitialProps = async (ctx) => {
const res = await fetch('https://api.github.com/repos/vercel/next.js')
const json = await res.json()
return { stars: json.stargazers_count }
}
export default HomePage
I might be missing something for sure, which I failed to figure out so far.
Any help will be really much appreciated. Thanks.

Next JS + Next Auth Protected page loads for seconds after redirection

I am trying to redirect to a protected page in the following way:
<button
onClick={() => {
router.push("/welcome");
}}
>
The welcome page is protected in the following way:
import { useSession, signOut } from "next-auth/client";
import { useRouter } from "next/router";
import { useEffect } from "react";
export default function Welcome() {
const [session, loading] = useSession();
const router = useRouter();
useEffect(() => {
if (!loading && !session?.accessToken) {
router.push("/auth/login");
}
}, [loading, session]);
if (!session) {
return <div></div>;
}
return (
<div>
<h1>Welcome Page</h1>
</div>
);
}
The welcome page works fine if I try to access it directly.
But if I use router.push(), it takes seconds to load. It mostly happens after going back from the welcome page with the browser's back button and then pressing the button again. Though it sometimes happens anyways.
What am I doing wrong? I was told it's normal behaviour for a dev environment, but it happens in production build too.
Here is a reproduction:
https://codesandbox.io/s/distracted-browser-fyjek
I solved the issue by wrapping my Component in _app.tsx with Provider like so:
<Provider session={pageProps.session}>
<Component {...pageProps} />
</Provider>;
and adding
export async function getServerSideProps(context: NextPageContext) {
const session = await getSession(context);
return {
props: { session },
};
}
to server side rendered pages like explained here.

How to get previous URL in Next.js?

How can I get the previous URL in Next.js?
I thought the values this.props.router.asPath and nextProps.router.asPath are different.
Actually, I want to call router.push after login. I know that router.back goes to the previous page. But it's possible to go to another site. The users having history stacks go to the previous page, the users not having history stacks go to / main page.
import { Component } from 'react'
import App, { Container } from 'next/app';
import ErrorComponent from '#/components/error'
export default class MyApp extends App {
render() {
console.log(this.props)
const { Component, pageProps, router } = this.props;
const props = {
...pageProps,
router
}
return (
<ErrorBoundary>
<Container>
<Component {...props} />
</Container>
</ErrorBoundary>
);
}
componentWillReceiveProps(nextProps) {
// previous page url /contents
console.log(this.props.router.asPath) // /about
console.log(nextProps.router.asPath) // /about
console.log('window.history.previous.href', window.history.previous) // undefined
}
}
How can I fix it? Or how can I get the previous URL to move page after login?
You find the Referer ( so the previous URL ) in the context of getServerSideProps or any other Data fetching methods
as
context.req.headers.referer
example in code
export async function getServerSideProps(context) {
console.log(context.req.headers.referer)
}
I've used Context do to this
In _app.tsx
import { HistoryProvider } from '../contexts/History'
const MyApp: React.FC<AppProps> = ({ Component, pageProps }) => {
return (
<ThemeProvider theme={theme}>
<Header />
<HistoryProvider>
<Component {...pageProps} />
</HistoryProvider>...
/contexts/History.tsx
import { useRouter } from 'next/router'
import React, { createContext, useState, useEffect, useContext } from 'react'
interface HValidation {
history: string[]
setHistory(data: string[]): void
back(): void
}
const HistoryContext = createContext<HValidation>({} as HValidation)
export const HistoryProvider: React.FC = ({ children }) => {
const { asPath, push, pathname } = useRouter()
const [history, setHistory] = useState<string[]>([])
function back() {
for (let i = history.length - 2; i >= 0; i--) {
const route = history[i]
if (!route.includes('#') && route !== pathname) {
push(route)
// if you want to pop history on back
const newHistory = history.slice(0, i)
setHistory(newHistory)
break
}
}
}
useEffect(() => {
setHistory(previous => [...previous, asPath])
}, [asPath])
return (
<HistoryContext.Provider
value={{
back,
history,
setHistory,
}}
>
{children}
</HistoryContext.Provider>
)
}
export function useHistory(): HValidation {
const context = useContext(HistoryContext)
return context
}
In any component, you can use
import { useHistory } from '../../contexts/History'
const ContentHeader: React.FC<ContentHeaderProps> = ({ title, hideBack }) => {
const { history, back } = useHistory() ...
I've used this component to back history ignoring links with hash (#), because the native router.back() was bugging when i have <a href="#someid" /> to scroll page to some page ids
I wanted to go back to last page, and not the last anchor
EDIT 01/04/2021
You can also set a fallback route for "back".
back(fallbackRoute?: string): void
function back(fallbackRoute?: string) {
for (let i = history.length - 2; i >= 0; i--) {
const route = history[i]
console.log({ route, pathname })
if (!route.includes('#') && route !== pathname) {
push(route)
const newHistory = history.slice(0, i)
setHistory(newHistory)
return
}
}
if (fallbackRoute) {
router.push(fallbackRoute)
}
}
I think you can implement a custom history in global state
Something like this
_app.js
import React from 'react';
import App, { Container } from 'next/app';
class MyApp extends App {
static async getInitialProps({ Component, ctx }) {
let pageProps = {};
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx);
}
return { pageProps };
}
state = {
history: [] // keep history items in state
};
componentDidMount() {
const { asPath } = this.props.router;
// lets add initial route to `history`
this.setState(prevState => ({ history: [...prevState.history, asPath] }));
}
componentDidUpdate() {
const { history } = this.state;
const { asPath } = this.props.router;
// if current route (`asPath`) does not equal
// the latest item in the history,
// it is changed so lets save it
if (history[history.length - 1] !== asPath) {
this.setState(prevState => ({ history: [...prevState.history, asPath] }));
}
}
render() {
const { Component, pageProps } = this.props;
return (
<Container>
<Component history={this.state.history} {...pageProps} />
</Container>
);
}
}
export default MyApp;
so then in your components you can navigate wherever you want within history
if (!history || !history.length) {
router.push('/');
} else {
router.push(history[history.length - 1]);
}
Hope this helps!
I was looking for a very simple way to do this since some of the answers here seem a bit complex for implementing something this simple. router.back() doesn't seem to work well in this scenario as, in my case, it'd go all the way back and out of my site some times.
So, I thought 🤔, what better way to do this than localStorage?
when I need to send the user to the '/login' route, I add the current route to localStorage
if (!auth.user) {
window.localStorage.setItem("path", router.asPath);
router.replace("/login");
return <div> redirecting to login... </div>;
}
and once the user sign-in, I send them back to the previous page (the route of which has been saved in the localStorage
if (auth.user) {
router.replace(localStorage.getItem("path") || "/");
return <div> Loading... </div>
);
}
You can observe the localStorage while testing to see what is going on. I hope this helps someone.
Let's say there's a /profile page which should be rendered iff user is logged in or else user should be redirected to /login, after user login on /login, it should be pushed to previous page (here/profile) but not on another website or New Tab.
In /profile this is how you should redirect to /login
Router.push('/login?referer=profile', '/login')
In /login after user is successfully logged in, use:
Router.push(Router.query.referer?.toString||'/')
Hope this helped.
I recently had this problem and used the following solution to route back to the previous page.
In my component I used the useRouter() hook from Next.js. This hook produces a router object which has the back() function. This function can be used on an <a> tag to redirect back in the following way.
const Component: React.FC = () => {
const router = useRouter();
return (
<>
<a onClick={() => router.back()}>Go back to the last page</a>
</>
);
};
Note that this function does not produce a URL that you can use as a value in the href, which is unfortunate. But I think this solution is simple yet effective.
Reference: https://nextjs.org/docs/api-reference/next/router#routerback
Simple Hook
Add this hook, and call it in your _app.tsx or where needed. You can compare it to router.pathname if you need to know what the change was.
const usePreviousRoute = () => {
const { asPath } = useRouter();
const ref = useRef<string | null>(null);
useEffect(() => {
ref.current = asPath;
}, [asPath]);
return ref.current;
};
This doesn't leave data behind once the page is closed (storage, cookies) and cleanly resets to null on next visit to the site.
import { useState, useEffect } from "react";
import { useRouter } from "next/router";
// in _app.js
function MyApp({ Component, pageProps }) {
const router = useRouter();
const [history, setHistory] = useState({ previous: null, current: router.asPath });
useEffect(() => {
setHistory((oldHistory) => ({ ...oldHistory, previous: oldHistory.current, current: router.asPath }));
}, [router.asPath]);
return (<Component {...{ ...pageProps, history }} />);
}
export default MyApp;
// in a page
function MyPage({ ...pageProps }) {
return (<span>Previous route: {pageProps.history?.previous || "/"}</span>);
}
export default MyPage;
I tried doing similar to iurii's answer. My _app.js looks like this (I was trying to integrate with segment.com so felt this need)
export default class MyApp extends App {
componentDidMount () {
const { asPath } = this.props.router;
this.setState(prevState => ({ history: [...prevState.history, asPath] }));
const isBrowser = typeof window !== 'undefined';
if(isBrowser) {
// For the first page load
console.log("Going to log first load --> referrer : ", document.referrer);
// this can get me the document.referrer properly, if I come to the website from a third party source like google search.
global.analytics.page(window.location.href,
{referrer: document.referrer}
)
}
}
static async getInitialProps ({ Component, router, ctx }) {
let pageProps = {}
if (Component.getInitialProps) {
pageProps = await Component.getInitialProps(ctx)
}
return { pageProps }
}
state = {
history: [] // keep history items in state
};
componentDidUpdate() {
const { history } = this.state;
const { asPath } = this.props.router;
// if current route (`asPath`) does not equal
// the latest item in the history,
// it is changed so lets save it
if (history[history.length - 1] !== asPath) {
global.analytics.page(window.location.href, {
referrer: history[history.length - 1] ? history[history.length - 1] : ""
})
// this simulates the document.referrer on pages after the user navigates
this.setState(prevState => ({ history: [...prevState.history, asPath] }));
}
}
So with a combination of history[history.length - 1] ? history[history.length - 1] : "" and const isBrowser = typeof window !== 'undefined'; I am able to simulate document.referrer for all cases. But I am missing one case, suppose , I am at google, my site landing page is A, then A points to B
Then google to A --> I get document.referrer as google
Then A to B --> I get document.referrer as A which is consistent with the behavior.
But Now If I refresh page B, then my document.referrer becomes google again.
I think I can save the last known previous URL in local storage , but that will be a anti-pattern as the browser back button can correctly take the user to the previous page (A), so somewhere the data is there already. Currently I can live with this solution as I only use this for analytics purpose on segment.com and google analytics, so refreshing will mess up my analytics numbers slightly, but still looking forward to a perfect solution so get exact data.
I can not get previous URL but with the code below I can find have a back URL or no:
typeof window !== 'undefined' && +window?.history?.state?.idx > 0
const back = async () => {
if (typeof window !== 'undefined' && +window?.history?.state?.idx > 0) {
await Router.back()
} else {
await Router.replace(fallbackURL)
}
}
A more simple way is
import { useRouter } from 'next/router';
const router = useRouter();
router.back()

Resources