Next JS server side rendering on Index.tsx with NextPage componet - next.js

tsx file having a Home component below it there is getServerSiderProps method.i want to get server side but props inside my Home component show undefined.
import type { NextPage } from 'next'
import Head from 'next/head'
import Banner from '../Components/Banner'
import Header from '../Components/Header'
import Posts from '../Components/Posts'
import { sanityClient, urlFor } from '../sanity'
const Home: NextPage = ({ posts }) => {
console.log(posts)
return (
<div>
<Head>
<title>Medium Blog</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Header></Header>
<Banner></Banner>
<Posts></Posts>
</div>
)
}
export default Home
//Server Side Rendering (SSR)
export const getServerSiderProps = async () => {
const query = `*[_type=="post"]{
_id,
title,
slug,
author->{
name,
image
},
description,
mainImage,
slug
}`
//Fetch
const posts = await sanityClient.fetch(query)
return { props: { posts } }
}
I want to get server side props every time it refresh my page but it became undefined.

Related

Google font loads very weirdly after the deployment to the vercel

I have deployed the next js app to the server using vercel. I have referenced the two google fonts in _document.js. While I am running the app locally both font load without any problem.
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document
{
static async getInitialProps(ctx)
{
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render()
{
return (
<Html>
<Head>
<link href="https://fonts.googleapis.com/css2?family=Crete+Round&family=Work+Sans:wght#500;600&display=swap" rel="stylesheet" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
index.js
import Head from "next/head";
import Script from "next/script";
import Banner from "../components/Banner";
import { fetchAPI } from "../lib/api";
import Articles from "../components/Articles";
export default function Home({ articles })
{
return (
<>
<Head>
<title>Life Sciencify - Explore the mystery of life with Science! </title>
</Head>
<Articles articles={articles} />
</>
);
}
export async function getServerSideProps()
{
const [articlesRes] = await Promise.all([
fetchAPI("/posts", { populate: ["cover", "category"] })
]);
console.log(articlesRes)
return {
props: {
articles: articlesRes.data
}
};
}
app.js
import Script from "next/script";
import "bootstrap/dist/css/bootstrap.css";
import "../styles/globals.css";
import { useEffect } from "react";
import Header from "../components/Header";
import SearchBlock from "../components/SearchBlock";
import Footer from "../components/Footer";
function MyApp({ Component, pageProps })
{
useEffect(() =>
{
import("bootstrap/dist/js/bootstrap");
}, []);
return (
<>
<Component {...pageProps} />
</>
);
}
export default MyApp;
After the deployment it is showing the weird behavior.
Initially When I am in the home page the page doesn't load any font.
Now, when I click the link Post1 or Post 2, it will be redirected to the detail page.
at first font is not loaded in this page too.
Now, after the page refresh the font gets loaded.
Now, when I go to the back page in the browser, the home page will have the font loaded. But again when the page is refreshed the font will be gone.
What is the causing the weird behavior?
I am running the application in the next js version of "12.1.6".
Referenced:
google-font-display
font-optimization
In the _document.js i used two google fonts separately and it is working now.
import Document, { Html, Head, Main, NextScript } from 'next/document'
class MyDocument extends Document
{
static async getInitialProps(ctx)
{
const initialProps = await Document.getInitialProps(ctx)
return { ...initialProps }
}
render()
{
return (
<Html>
<Head>
<link href="https://fonts.googleapis.com/css2?family=Work+Sans:wght#500;600&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Crete+Round&display=swap" rel="stylesheet" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument
and in index.js, change server side rendering to static props:
export async function getStaticProps()
{
const [articlesRes] = await Promise.all([
fetchAPI("/posts", { populate: ["cover", "category"] })
]);
console.log(articlesRes)
return {
props: {
articles: articlesRes.data
}
};
}
After this changes I deployed to vercel it worked fine, again after some time i changes to getServerSideProps, it was not working. So, the culprit was getServerSideProps with google font.

How to create dynamic route for top-level page in NEXT?

To support these URLs:
/account?tab=profile
/account?tab=pass
/account?tab=points
I know that I can change them to:
/account/profile
/account/pass
/account/points
And then create this route:
/pages/account/[tab].js
But this means that the accoun is a directory, not a file.
I want to have a account.js top-level file, and have a route for query strings on it.
I don't know how to do it. Something like /account?[tab] route. Is it possible?
import type { GetServerSideProps, NextPage } from 'next'
import Head from 'next/head'
import { ParsedUrlQuery } from 'querystring';
import styles from '../styles/Home.module.css'
const About: NextPage<{ query: ParsedUrlQuery }> = ({ query }) => {
console.log(query);
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<h1>About</h1>
</main>
</div>
)
}
export const getServerSideProps: GetServerSideProps = async ({ query }) => {
return {
props: {
query
}
}
}
export default About

How to provide all route params to getStaticPaths?

I am trying to create a dynamic page component [page].js that can display pages dynamically. The route is set dynamically to
localhost:3000/[channel]/[store]/page/[slug]
I provided slug as params in paths array in getStaticPaths but I'm getting the following error:
Server Error
Error: A required parameter (channel) was not provided as a string in getStaticPaths for /[channel]/[store]/page/[slug]
This error happened while generating the page. Any console logs will be displayed in the terminal window.
It looks like it's asking for channel and store params?
I do have channel and store data in getStaticProps but not in getStaticPaths.
I've set up getStaticPaths with slug param but I'm not sure how to find out channel and store params here.
import {fetchLayout} from "../../../../http/server/fetchLayout";
import Layout from "../../../../views/layout/Layout/Layout";
import Head from "../../../../resources/components/Head/Head";
import {fetchPage} from "../../../../http/server/fetchPage";
import {fetchPages} from "../../../../http/server/fetchPages";
const Page = ({ layout, page }) =>
{
if ( page && layout ) {
const { title, meta_title, description, keywords } = page
return (
<Layout data={ layout }>
<Head title={ meta_title } >
<meta name="description" content={ description } />
<meta name="keywords" content={ keywords } />
</Head>
<h1 style={{ textAlign: `center`, margin: `50px 0` }}>{ title }</h1>
</Layout>
)
}
return null
}
export const getStaticProps = async ({ params }) =>
{
const layout = await fetchLayout()
// const { channel, store } = layout.region
const page = await fetchPage( params.slug )
return { props: { layout, page }}
}
export const getStaticPaths = async () =>
{
const pages = await fetchPages()
const paths = pages.map(({ slug }) => ({ params: { slug }}))
return { paths, fallback: true }
}
export default Page

Dynamic component imported stuck in loading phase : next js

I am new to next js. In my project I need to display youtube videos. I have an api which provides me the video ids to show with its meta details. I wanted to create dynamic pages for each videos. I am using react-player as player.
Here is my code
[videoId].tsx
import Head from 'next/head';
import { useRouter } from 'next/router'
import Layout from '../../components/layout';
import { IVideoItem } from '../../models/videos.model';
import VideoContainer from '../../components/videos-page/video-container';
import { getVideosPaths, getVideosPageTitle, getVideosPageDescription, getVideosData } from '../../services/videos-page.services';
export default function VideoPage({videoInfo} :IVideosPageProp) {
const router = useRouter()
if (router.isFallback) {
return <div>Loading...</div>
}
return(
<>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta charSet="utf-8" />
<title>{getVideosPageTitle(videoInfo)}</title>
<meta name="description" content={getVideosPageDescription(videoInfo)} />
<meta property="og:title" content={getVideosPageTitle(videoInfo)} key="ogtitle" />
<meta property="og:description" content={getVideosPageDescription(videoInfo)} key="ogdesc" />
</Head>
<VideoContainer data={videoInfo} />
</>
)
}
export async function getStaticPaths() {
const paths = await getVideosPaths()
//console.log('paths: ',paths);
return {
paths,
fallback: false
}
}
export async function getStaticProps({ params }:IVideosPageStaticProp) {
const {videoId} = params;
const videoInfo = await getVideosData(videoId)
return {
props: {
videoInfo
}
}
}
interface IVideosPageProp {
videoInfo: IVideoItem
}
interface IVideosPageStaticPropParams {
videoId: string
}
interface IVideosPageStaticProp {
params: IVideosPageStaticPropParams
}
video-container.tsx
import { Row, Col } from 'react-bootstrap'
import { IVideoItem } from '../../models/videos.model';
import styles from './videos-container.module.scss';
import VideoTag from '../home/videos-block/video-tag';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { faEye, faThumbsUp, faThumbsDown } from '#fortawesome/free-solid-svg-icons';
import moment from 'moment';
import dynamic from 'next/dynamic';
const ReactPlayer = dynamic(
() => import('react-player'),
{ loading: () => <p>...</p>, ssr: false }
)
export default function VideoContainer({data} :IVideosPageProp){
const videoInfo:IVideoItem = data;
const videoTag = [{"tagName": "Foo", "tagId": 1}]
const fallBackElement = () => {
return <img src={videoInfo.default_thumbnail_url} width="100%"/>
}
return (
<div className={styles['videos-container']}>
<ReactPlayer
url={`https://youtu.be/${data.video_id}`}
controls
width = "100%"
light={true}
playing={true}
fallback={fallBackElement()}
config={{
youtube: {
playerVars: { showinfo: 1 }
}
}}
/>
<div className={styles['videos-body']}>
<div className={styles['tag-list-container']}>
{videoTag.map((tag, index) =>{
return <VideoTag videoTag={tag} key={index}/>
})}
</div>
<div className={styles['video-title']}>
{videoInfo.title}
</div>
<Row className={styles['video-numbers']}>
<Col md={2} xs={2}><FontAwesomeIcon icon={faEye} className={styles['views-icon']} />{videoInfo.views_count}</Col>
<Col md={2} xs={4}>{moment(new Date(videoInfo.published_at)).format('Do MMMM YYYY')}</Col>
<Col md={4} xs={2}></Col>
<Col md={2} xs={2}><FontAwesomeIcon icon={faThumbsUp} className={styles['views-icon']} />{videoInfo.like_count}</Col>
<Col md={2} xs={2}><FontAwesomeIcon icon={faThumbsDown} className={styles['views-icon']} />{videoInfo.dislike_count}</Col>
</Row>
<div className={styles['video-description']}>
{videoInfo.description}
</div>
</div>
</div>
)
}
interface IVideosPageProp {
data:IVideoItem
}
When I run yarn dev the page is loading properly and the video player is rendering and working as expected. But when I run next buld and after that next start, the page is loading, but player is not loading. Insted it shows the "Loading..." message on the page, I refreshed several times, no luck. Not able to understand the issue. Can any one help?
Update 1:
The page is rendering with video title, video description etc. But the dynamically imported video player is not rendered. At the place of video player, it shows 'Loading...'.
Not sure if you can dynamically load from the node_module, like this:
const ReactPlayer = dynamic(
() => import('react-player'),
{ loading: () => <p>...</p>, ssr: false }
)
But you should be able to do this by creating a react-player component first, then dynamic import it like this:
// create a component named Player.js
import ReactPlayer from 'react-player';
const Player = props => (<ReactPlayer {...props}/>)
export default Player;
// then dynamic import it:
const Player = dynamic(
() => import('../components/Player'),
{ ssr: false }
)
// Then use <Player> with the same props

How can I get (query string) parameters from the URL in Next.js?

When I click on a link in my /index.js, it brings me to /about.js page.
However, when I'm passing parameter name through URL (like /about?name=leangchhean) from /index.js to /about.js, I don't know how to get it in the /about.js page.
index.js
import Link from 'next/link';
export default () => (
<div>
Click{' '}
<Link href={{ pathname: 'about', query: { name: 'leangchhean' } }}>
<a>here</a>
</Link>{' '}
to read more
</div>
);
Use router-hook.
You can use the useRouter hook in any component in your application.
https://nextjs.org/docs/api-reference/next/router#userouter
pass Param
import Link from "next/link";
<Link href={{ pathname: '/search', query: { keyword: 'this way' } }}><a>path</a></Link>
Or
import Router from 'next/router'
Router.push({
pathname: '/search',
query: { keyword: 'this way' },
})
In Component
import { useRouter } from 'next/router'
export default () => {
const router = useRouter()
console.log(router.query);
...
}
Using Next.js 9 or above you can get query parameters:
With router:
import { useRouter } from 'next/router'
const Index = () => {
const router = useRouter()
const {id} = router.query
return(<div>{id}</div>)
}
With getInitialProps:
const Index = ({id}) => {
return(<div>{id}</div>)
}
Index.getInitialProps = async ({ query }) => {
const {id} = query
return {id}
}
url prop is deprecated as of Next.js version 6:
https://github.com/zeit/next.js/blob/master/errors/url-deprecated.md
To get the query parameters, use getInitialProps:
For stateless components
import Link from 'next/link'
const About = ({query}) => (
<div>Click <Link href={{ pathname: 'about', query: { name: 'leangchhean' }}}><a>here</a></Link> to read more</div>
)
About.getInitialProps = ({query}) => {
return {query}
}
export default About;
For regular components
class About extends React.Component {
static getInitialProps({query}) {
return {query}
}
render() {
console.log(this.props.query) // The query is available in the props object
return <div>Click <Link href={{ pathname: 'about', query: { name: 'leangchhean' }}}><a>here</a></Link> to read more</div>
}
}
The query object will be like: url.com?a=1&b=2&c=3 becomes: {a:1, b:2, c:3}
For those looking for a solution that works with static exports, try the solution listed here: https://github.com/zeit/next.js/issues/4804#issuecomment-460754433
In a nutshell, router.query works only with SSR applications, but router.asPath still works.
So can either configure the query pre-export in next.config.js with exportPathMap (not dynamic):
return {
'/': { page: '/' },
'/about': { page: '/about', query: { title: 'about-us' } }
}
}
Or use router.asPath and parse the query yourself with a library like query-string:
import { withRouter } from "next/router";
import queryString from "query-string";
export const withPageRouter = Component => {
return withRouter(({ router, ...props }) => {
router.query = queryString.parse(router.asPath.split(/\?/)[1]);
return <Component {...props} router={router} />;
});
};
Get it by using the below code in the about.js page:
// pages/about.js
import Link from 'next/link'
export default ({ url: { query: { name } } }) => (
<p>Welcome to About! { name }</p>
)
I know 2 ways to do this:
A Server-Side way, and a Client-Side way.
Method #1: SSR (Server-Side Rendering):
You should use Query Context for that page.
So use getServerSideProps instead of getStaticProps
import React from "react";
export async function getServerSideProps(context) {
const page = (parseInt(context.query.page) || 1).toString();
// Here we got the "page" query parameter from Context
// Default value is "1"
const res = await fetch(`https://....com/api/products/?page=${page}`);
const products = await res.json();
return {props: {products: products.results}}
// will be passed to the page component as props
}
const Page = (props) =>{
const products = props.products;
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>);
}
export default Page
The reason is that: this data cannot be pre-rendered ahead of user's request, so it must be Server-Side Rendered (SSR) on every request.
Static Pages: Use getStaticProps
Changing Content: use getServerSideProps
And here the content is changing based on query Parameters
Reference: https://nextjs.org/docs/api-reference/data-fetching/get-server-side-props
Method #2: Next Router (Client Side):
import {useState, useEffect} from "react";
import { useRouter } from 'next/router'
const Page = () =>{
const [products, setProducts] = useState([]);
const [page, setPage] =useState((useRouter().query.page || 1).toString());
// getting the page query parameter
// Default value is equal to "1"
useEffect(()=>{
(async()=>{
const res = await fetch(`https://....com/api/products/?page=${page}`);
const products = await res.json();
setProducts(products.results);
// This code will be executed only once at begining of the loading of the page
// It will not be executed again unless you cahnge the page
})()
},[page]);
return (
<ul>
{products.map((product) => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
export default Page
Reference: https://nextjs.org/docs/api-reference/next/router
If you need to retrieve a URL query from outside a component:
import router from 'next/router'
console.log(router.query)
import { useRouter } from 'next/router';
function componentName() {
const router = useRouter();
console.log('router obj', router);
}
We can find the query object inside a router using which we can get all query string parameters.
Using {useRouter} from "next/router"; helps but sometimes you won't get the values instead u get the param name itself as value.
This issue happens when u are trying to access query params via de-structuring like:
let { categoryId = "", sellerId = "" } = router.query;
and the solution that worked for me is try to access the value directly from query object:
let categoryId = router.query['categoryId'] || '';
let sellerId = router.query['sellerId'] || '';
Post.getInitialProps = async function(context) {
const data = {}
try{
data.queryParam = queryString.parse(context.req.url.split('?')[1]);
}catch(err){
data.queryParam = queryString.parse(window.location.search);
}
return { data };
};
import { useRouter } from 'next/router'
const Home = () => {
const router = useRouter();
const {param} = router.query
return(<div>{param}</div>)
}
Also you can use getInitialProps, more details refer the below tutorial.
get params from url in nextjs
What worked for me in Nextjs 13 pages in the app directory (SSR)
Pass params and searchParams to the page:
export default function SomePage(params, searchParams) {
console.log(params);
console.log(searchParams);
return <div>Hello, Next.js!</div>;
With some builds there may be a bug that can be solved by adding:
export const dynamic='force-dynamic';
especially when deploying on Vercel.
ref: https://beta.nextjs.org/docs/api-reference/file-conventions/page#searchparams-optional
https://github.com/vercel/next.js/issues/43077

Resources