How to implement Server Sider Rendering (SSR) for a NextJS component? - reactjs

I want to implement Server Sider Rending SSR for a NextJS Typescript component. It's possible to do SSR for a page using getServerSideProps but not found any way for non page child components. It works fine under page folder but no luck outside of page components.
Say I have created a component with API under components folder, here getServerSideProps is not working and even SSR also not working. and can't see the content in page view source DOM.
/components/user.tsx
import React from 'react';
import {useEffect,useState} from 'react';
interface User {
id:number;
name: string;
username: string;
email:string;
}
const Users = () => {
const [data,setData]= useState([])
useEffect(()=>{
fetch("https://jsonplaceholder.typicode.com/users").then((result)=>{
result.json().then((resp)=>{
setData(resp);
})
})
},[])
console.log(data)
return (
<div>
<ul>
{data.map((data: User)=>
<li key={data.id}>
<h2>{data.name}</h2>
<h3>{data.username}</h3>
<h4>{data.email}</h4>
</li>
)}
</ul>
</div>
)
}
export default Users
/pages/user.tsx
import React from 'react';
import Users from "../components/Users";
function user() {
return (
<section>
<h1>User - Component</h1>
<main>
<Users />
</main>
</section>
);
};
export default user;
Can you please help how to implement SSR for components which located out side of pages folder?

As it notes in the documentation
getServerSideProps can only be exported from a page. You can’t export it from non-page files.
This rules out getServerSideProps, now for the ask itself
Two options
setup a corresponding pages/component page
Setup a custom server and use the following in next.config.js - This allows routing to be on you without using the pages as the only place things get served from.
module.exports = {
useFileSystemPublicRoutes: false,
}

Related

Fetch navigation from API in the next.js and render it at initial load of the page for better SEO

i am very new to React/NEXT.JS and i guess i am facing a issue where i want to fetch data for my dynamic navigation bar, which is available on all the pages. I am using context API to set the value and fetch 1 time at the initial render of the whole document. Then it should show navigation items directly.
Here is how i am trying to do it.
import { createContext, useState, useEffect } from "react";
import Layout from "../components/layout";
import "../styles/globals.css";
import BasicProviders from "../helpers/BasicProviders";
export const NavContext = createContext();
function MyApp({ Component, pageProps, navigationItems }) {
const [navitems, setNavitems] = useState(navigationItems);
return (
<NavContext.Provider value={{ navitems }}>
<Layout>
<Component {...pageProps} />
</Layout>
</NavContext.Provider>
);
}
MyApp.getInitialProps = async (ctx) => {
const res = await fetch("http://www.myserver.com/navigation");
console.log(res);
return { navigationItems: res };
};
export default MyApp;
Now i have folder structure like this:
- components (in root directory of next.js)
- layout.js
- /Header
- DesktopNav.js <<<<<--------------- here i want to send my value of **navitems**
- Header.js
This is how my DesktopNav.js is looking :
import Link from "next/link";
import React, { useContext } from "react";
import NavContext from "../../stores/NavContext";
import DesktopSubMenuLoop from "./DesktopSubMenuLoop";
export default function DesktopNav() {
const { navitems } = useContext(NavContext);
return (
<nav>
<DesktopSubMenuLoop navitems={navitems}></DesktopSubMenuLoop>
</nav>
);
}
Somehow it is sending the data to the component. But everytime i click on the Navigation Link it reloads the complete page. Not sure how i can achieve this. For the time being i am using useEffect to do achieve the same. But that is not SEO compatible.
Problems need to be solved here:
API should be called once for whole page. As the navigation will stay same for all the pages.
Page routing should be as smooth as it is with static tags. Currently it reloads the page like a static HTML web page.
Possible solutions i though of:
is there any way i can fetch the navitems on _document.js and send to _app.js?
Calling the API on each page with getServerSideProps but that will make all of my pages non-static for internal pages as well at the build time.
Any solution i can get on this will be really helpful and thankful.
#juliomalves for you:
MainNavigation Loop currently i am using useEffect to display the navigation, but as per my understanding its not good for SEO.
You can check my loop here DesktopSubMenuLoop.js
import Link from "next/link";
import React from "react";
export default function DesktopSubMenuLoop({ navitems, isSubmenu = false }) {
return (
<ul className={isSubmenu ? "sub-menu-style" : ""}>
{navitems.map((item, index) => (
<li key={item.id}>
<Link href={item.slug !== "/" ? `/${item.slug}` : item.slug}>
<a>
{item.title}{" "}
{item.children && item.children.length > 0 && (
<i className="icon-arrow-down"></i>
)}
</a>
</Link>
{item.children && item.children.length > 0 && (
<DesktopSubMenuLoop
key={item.id}
navitems={item.children}
isSubmenu={true}
></DesktopSubMenuLoop>
)}
</li>
))}
</ul>
);
}

Get the Gatsby Link destination and conditionally render exit animation with Framer Motion

I've built a small website to learn more about page transition with Gatsby and Framer Motion and Styled Components.
[SPOILER]: My problem to be solved is at the end of the code blocks
The way it's currently working is simple:
An homepage with a list of projects
export default function Home() {
return (
<Layout>
<Welcome />
<WorkList />
<Footer />
</Layout>
)
}
A project page template that generate each project thanks to createPages (here is a simplified version)
import React, { useState, useRef, useContext, useEffect } from "react"
import { Link } from "gatsby"
// Components
...
// Data
import Projects from "../data/works.json"
// Styles
...
// Variants
...
const Project = ({ pageContext }) => {
const project = Projects.find(({ id }) => id === pageContext.id)
// lots of functions here
return (
<Layout>
<ProjectWrapper>
<Container>
<ProjectContent>
<BackgroundLines />
<ProjectContentInner>
<ProjectHeader>
<!-- here the header logic -->
</ProjectHeader>
<ProjectBlocks>
<!-- here the content logic -->
</ProjectBlocks>
</ProjectContentInner>
<ProjectFooter>
<!-- here the footer logic -->
</ProjectFooter>
</ProjectContent>
</Container>
</ProjectWrapper>
</Layout>
)
}
export default Project
The Layout component is holding the navigation
// Components
import Header from "./header"
// Styles
import { GlobalStyle } from "../styles/globalStyles"
const Layout = ({ children }) => {
return (
<div className="app">
<GlobalStyle />
<Header />
<main>{children}</main>
</div>
)
}
export default Layout
and last but not least, the gatsby.browser.js wrapped with the AnimatePresence and the Context Provider
import React from "react"
import { AnimatePresence } from "framer-motion"
import { LocationProvider } from "./src/context/locationContext"
export const wrapPageElement = ({ element }) => (
<LocationProvider>
<AnimatePresence exitBeforeEnter>{element}</AnimatePresence>
</LocationProvider>
)
export const shouldUpdateScroll = () => {
return false
}
So what I want to do seemed easy but it turned out that is not (at least for me).
I've currently made a beautiful transition between 2 projects, similar to the one you could see in here.
If you scroll to the bottom of the page, you can see that the next project's header is shown as a preview and once you click on it, it will smoothly transition to the next project page.
Awesome.
BUT, but this transition is a problem when the user clicks on the link in the navigation that takes him to the home or to another page.
I don't want to have the same exit transition, where some elements disappear while others overlaps, and I don't want the same timing. I want to do something completely different, based on where I'm headed to.
What I thought of as a solution, is to conditionally render exit transition in framer motion, to have different exit animation based on some variables.
I want to be able to track the Link Destination before the component unmount in order to be able to conditionally render an exit transion in Framer Motion
Since, as you may have seen, the navigation isn't inside the project.js I tried with createContext and useContext, getting the location.pathname to have an origin state and a e.target.pathname on Link to have a destination state. This doesn't actually works because everything seems to get a rerender.
I just provided the pieces of codes that seemed crucial to understand the overall structure, but I can go deeper with the way I've built variants or the current exit animations.
I'm not sure if it will help but you can get the props as any React component in the wrapPageElement:
export const wrapPageElement = ({ element, props }) => {
console.log("props", props)
if(props.location.pathname==='/'){
//return some other animation stuff
}
return <LocationProvider {...props}>
<AnimatePresence exitBeforeEnter>{element}</AnimatePresence>
</LocationProvider>
}

React TypeScript 16.8 - How to change the title of the page without Helmet

I've made a 404 not found page, but when I browse to it I want the title of the page to change, I dont want to use Helmet but I cant seem to get constructor or componentDidMount() to work, Im using the React 16.8.6 and Create React App Typescript was my starting point.
import React from 'react';
import logo from '../images/logo.svg';
const NotFound: React.FC = () => {
return (
<div className="home-grid">
<header className="center-logo">
<img src={logo} alt="My Logo" />
</header>
<footer className="home-footer">
Error 404 page not found
</footer>
</div>
);
}
export default NotFound;
you can just set the title of the page with regular javascript.
document.title = '...'
componentDidMount can only be used on classes, for function components use (useEffect) hooks instead.
useEffect(() => {
document.title = 'something...';
}, [])

Initialize script in componentDidMount – runs every route change

I am working on a navbar for my react app (using gatsbyjs to be precise). In the navbar I have a marquee that I initialize in the navbar component in componentDidMount.
It works as intended, but upon every route change componentDidMount will run again which results in the marquee speeding up for every page change, making it go faster and faster.
Is this expected behaviour? And if so, how do I make sure that the script is only run once?
navbar.js
import React from 'react';
import { Link } from 'gatsby';
import styles from '../styles/navbar.module.css';
import NewsMarquee from './newsMarquee';
import Marquee3k from 'marquee3000';
const topLevelNav = [
{
href: '/news',
label: <NewsMarquee/>,
extraClass: styles.navLinkNews,
mediaQueryClass: styles.navLinkHiddenSmall,
},
];
export default class Navbar extends React.Component {
componentDidMount() {
Marquee3k.init();
}
render() {
return (
<div>
<header className={styles.navbar} role="banner">
<nav className={styles.nav}>
{topLevelNav.map(({ href, label, extraClass = '', mediaQueryClass = '' }) => (
<Link
key={label}
to={href}
className={`${styles.navLink} ${extraClass} ${mediaQueryClass} ${menuItemsHidden}`}
activeClassName={styles.navLinkActive}
>
{label}
</Link>
))}
</nav>
</header>
</div>
)
}
}
newsMarquee.js
import React from 'react';
import { StaticQuery, graphql } from "gatsby";
import styles from '../styles/newsMarquee.module.css';
export default () => (
<StaticQuery
query={graphql`
query {
allMarkdownRemark(sort: { fields: [frontmatter___date], order: DESC } limit: 10) {
totalCount
edges {
node {
id
frontmatter {
title
date(formatString: "YYYY.MM.DD")
}
fields {
slug
}
}
}
}
}
`}
render={data => (
<div className={`marquee3k ${styles.marquee}`}>
<div>
{data.allMarkdownRemark.edges.map(({ node }) => (
<span className={styles.marqueeItem} key={node.id}>
{node.frontmatter.date} {node.frontmatter.title}
</span>
))}
</div>
</div>
)}
/>
)
Since I'm using GatsbyJS I went with this plugin from V1, which makes my layout component persist across pages.
gatsby-plugin-layout
This plugin enables adding components which live above the page components and persist across page changes.
This can be helpful for:
Persisting layout between page changes for e.g. animating navigation
Storing state when navigating pages
Custom error handling using componentDidCatch
Inject additional data into pages using React Context.
This plugin reimplements the behavior of layout components in gatsby#1, which was removed in version 2.

Meteor + React + createcontainer

Using react with meteor here, I have main component called App, and it wraps page layout (Header, Sidebar, Right-sidebar).
export default class App extends Component {
render() {
return (
<div>
<nav className="navigation">
<Header />
<Sidebar />
</nav>
<div className="content">
<Subnavbar />
<div className="container">
{this.props.children}
</div>
</div>
<Rightsidebar />
</div>
);
}
};
I'm trying to setup authentication system using Meteor's built in auth system. using "accounts-password" package.
To my knowldge, I need to use createContainer from 'meteor/react-meteor-data' to inject auth params to components.
Similar to this example:
import { createContainer } from 'meteor/react-meteor-data';
import MainPage from '../pages/MainPage.jsx'
export default MainContainer = createContainer(({params}) => {
const currentUser = Meteor.user();
return {
currentUser,
};
}, MainPage);
However in the above example, it only injects the parms to a single component, how can I go about injecting auth info to all components in my app (Header, Sidebars ..etc)
Your help is highly appreciated.
Thank you
If you wrap App in createContainer, then App will have a prop currentUser. It can then be the responsibility of App to pass the currentUser prop to all of your components. If you find yourself passing around currentUser far too much, then you can wrap only the components that need currentUser in createContainer.
In that case you would have HeaderContainer, SidebarContainer, etc, each being wrapped with createContainer.

Resources