gatsby ssr - add script to header before other plugins - reactjs

I am using Gatsby and need to head a script in the header BEFORE other plugin.
If I add it via gatsby-ssr.js
exports.onRenderBody = ({ setHeadComponents }) => setHeadComponents([#MY-SCRIPT#]);
it's being added last.
How can I add it BEFORE other plugins?

In addition to onRenderBody, use onPreRenderHTML to reorder your scripts.
exports.onRenderBody = ({ setHeadComponents }) =>
setHeadComponents([
<script key='myscript' src='...' />
]);
exports.onPreRenderHTML = ({ getHeadComponents, replaceHeadComponents }) => {
const headComponents = getHeadComponents()
// reorder your array with the sort method, by putting your item at top
const orderedComponents = headComponents.sort((item) => (item.key === 'your-key' ? -1 : 1)); const orderedComponents = reorder(headComponents)
replaceHeadComponents(orderedComponents)
}
For more info, see the Gatsby docs on SSR API.

2022 update
Since the release of the Script Gatsby component (powered by Partytown) it's much easier adding third-party scripts. Just:
import React from "react"
import { Script } from "gatsby"
function YourPage() {
return <Script src="https://my-example-script" />
}
export default YourPage
Why you use gatsby-ssr.js file if you can use <Helmet> tag, maybe it fits you. You just need to use it like this in any component:
import React from "react"
import Helmet from "react-helmet"
import Layout from "../components/layout"
import SEO from "../components/seo"
const IndexPage= () => (
<Layout>
<SEO title="Index page" />
<Helmet>
<script src="https://whatever.com" type="text/javascript"/>
<script src="https://whatever2.com" type="text/javascript"/>
</Helmet>
</Layout>
)
export default IndexPage
The snippet above will load your scripts inside <head> tag on the same order you've placed it.
If you need some kind of ordering and async approach, you can use gatsby-ssr, across onRenderBody and onPreRenderHTML.

Related

Gatsby dynamic routes: 404 instead of component, can't configure

I made a simple site with Gatsby.js and can't configure dynamic routes.
I have index.js page (was automatically created by react), that looks like:
import * as React from 'react'
const IndexPage = () => {
return (
<Layout
pageTitle="Home Page"
>
Some text for my main page
</Layout>
)
}
export const Head = () => <title>Home Page</title>
export default IndexPage
Layout components includes Header that looks like:
import React, { useState, useEffect } from 'react';
import { Link } from 'gatsby';
const Header = () => {
return (
<Wrapper style={{ *some styles* }}>
<Link to="/">Index</Link>
<Link to="/projects">Projects</Link >
</Wrapper>
)
};
export default Header;
I have my Projects page that looks like this:
import * as React from 'react'
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import Layout from '../layout'
const Projects = () => {
return (
<BrowserRouter>
<Layout>
<Wrapper>
<Routes>
<Route path="projects/:projectID/" component={ProjectDetails} />
</Routes>
<MyProjectLink to="/projects/1">
Project 1
</MyProjectLink>
<MyProjectLink to="/projects/2">
Project 2
</MyProjectLink>
</Wrapper>
</Layout>
</BrowserRouter>
)
}
export const Head = () => <title>Our Projects</title>
export default Projects
And I have my ProjectDetails component:
import React from 'react';
import { useParams } from 'react-router';
import Layout from '../../pages/layout';
const ProjectDetails = () => {
const { projectID } = useParams();
return (
<Layout>
<Wrapper>
<h2>Project {projectID}</h2>
</Wrapper>
</Layout>
);
}
export default ProjectDetails;
The problem is that when I navigate to localhost:8000/projects/1 (or "2", or "3", or "100500") I see a 404 page instead of my ProjectDetails component.
I've tried wrapping the index page to BrowserRouter and move the routes with my route there, but that's a dumb idea in my opinion (and it doesnt work).
Did I miss something? Some features of Gatsby (v5) that I don't know about? I'm new to Getsby and to be honest I thought that dynamic routes here work the same way as in React Router.
Gatsby extends its routing from React, however, the way to create routes is slightly different.
As far as I understand your code, you are trying to create a template page for projects: this can be simply done by creating a file inside /templates folder. A simple component like this should work:
const Projects = ({ data }) => {
return (
<Layout>
<Wrapper></Wrapper>
</Layout>
);
};
export const Head = () => <title>Our Projects</title>
export default Projects
This template, as long as you use it when creating pages (using either gatsby-node.js or File System Route API) will be used to hold each specific project data.
Each project data will be queried using GraphQL and held inside props.data but without knowing your source (can be markdown, JSON, CMS, etc) I can't provide a sample query.
Once Gatsby infers its GraphQL nodes from your data source, you can use it to get all projects, a specific project, or any other GraphQL data you need on any page/template (page query) or even using static queries.
The idea should be similar to:
// gatsby-node.js
projects.forEach(({ node }, index) => {
createPage({
path: node.fields.slug,
component: path.resolve(`./src/templates/project.js`),
context: {
title: node.title,
},
})
})
In your gatsby-node.js (or File System Route API) you get all projects, loop through them and createPage for each project. The path (URL) for each project will be the slug field (node.fields.slug) but you can use whatever you want. Gatsby will create dynamic pages based on this field.
Then you decide which component will be used as a template: path.resolve(`./src/templates/project.js`) in this case and finally, you populate the context to add a unique value (title in this case: again, this can be an id, the slug, etc). This value will be used to filter the node in the template.
In your Project template:
export const query = graphql`
query ($title: String) {
mdx(title: {eq: $title}) {
id
title
}
}
`
In this case, I'm using markdown-based sources (that's why the mdx node) and this node is filtered by the title ($title) using the context value. The data will be inside props.data of the template. Again, if you want to fetch all projects you will have available an allMarkdown or allMdx (or allJSON...) depending on the source node)

React SSR blinks when starting client

simplifying my post:
my ssr webpage blinks when starting client which means page renders server side rendered html then goes blank and then it starts loading everything all over again.
going through the details:
i'm working on a react project which we decided to change it from being rendered in client to render in server. the project includes react-router-dom ,redux and react-redux ,material-ui which comes with react-jss ,loadable/component ,also handling the head elements by react-helmet-async ,and in ssr it's using express.js which seems to be a must.
for react-router-dom i did everything that is on the docs. using BrowserRouter in client and StaticRouter in ssr and passing a context object to it.
for redux and react-redux i saved preloaded_state in a variable in window and fetched it client side then pass it to store.also fetched some external data to get the content on the source of the page.so i have some requests and data fetching in ssr.
for material-ui i created a new serverSideStyleSheet and collected all the styles from all over the project.
for react-helmet-async i've got different Helmet tags for each page that collects different title ,description and ... individualy.there is also a helmetProvider wrapper for csr and ssr.
at first i used react-helmet but it wasn't compatible with renderToNodeStream.i didn't change react-helmet-async although i'm not using renderToNodeStream but kept it to migrate to renderToNodeStream one day hopefully.
about express.js i'm using it as the docs say but after i added loadable/component i wasn't able to get a successful server side rendering by just adding app.get('*' , ServerSideRender) .so i had to add every url i wanted to render in server in app.get(url ,ServerSideRender).
the other thing about the project is that i didn't eject and created it with create-react-app and there is no webpack config or babelrc but instead i'm using craco.config.js
the last thing is that i've excluded index.html from ssr and instead i've made the tags myself in SSR.js file so index.html gets rendered just in client. and i was so careful about writing tags in ssr exactlly like they are in index.html
solution but not the solution:
this problem is occuring because i use loadable component in my Router.js. so when i import components the normal way there is no blink and everything is fine but unused js decreases my page's perfomance score. i need loadable component stop making the page blink.
diving into the code:
just the client
index.html : rendered just in client
<!DOCTYPE html>
<html lang="fa" dir="rtl">
<head>
<meta name="robots" content="noindex, nofollow" />
<meta data-rh="true" name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no" />
<link href="%PUBLIC_URL%/fonts.css" rel="stylesheet" />
</head>
<body>
<div id="root"></div>
<script src="%PUBLIC_URL%/main.js"></script>
</body>
</html>
index.js : rendered just in client
import React from 'react'
import ReactDOM from 'react-dom'
import {loadableReady} from '#loadable/component'
import App from './App'
import {BrowserRouter} from 'react-router-dom'
import {HelmetProvider} from 'react-helmet-async'
import { Provider } from 'react-redux'
loadableReady(() => {
const preloadedState = window.__PRELOADED_STATE__
delete window.__PRELOADED_STATE__
ReactDOM.hydrate(
<BrowserRouter>
<HelmetProvider>
<Provider store={store(preloadedState)}>
<App />
</Provider>{" "}
</HelmetProvider>
</BrowserRouter>,
document.getElementById("root")
);
})
just the server
ssrIndex.js
require('ignore-styles')
require('#babel/register')({
ignore: [/(node_modules)/],
presets: ['#babel/preset-env', '#babel/preset-react'],
plugins: [
'#babel/plugin-transform-runtime',
'#babel/plugin-proposal-class-properties',
'babel-plugin-dynamic-import-node',
'#babel/plugin-transform-modules-commonjs',
'#loadable/babel-plugin',
],
})
// Import the rest of our application.
require('./SSR.js')
SSR.js
import React from 'react'
import express from 'express'
import ReactDOMServer from 'react-dom/server'
import {StaticRouter} from 'react-router-dom'
import {Provider} from 'react-redux'
import ServerStyleSheets from '#material-ui/styles/ServerStyleSheets'
import {HelmetProvider} from 'react-helmet-async'
import {ChunkExtractor, ChunkExtractorManager} from '#loadable/server'
import path from 'path'
import App from './App'
import store from './redux/store'
import template from './utils/template'
const PORT = 8080
const app = express()
const renderPage = (req, res, preload) => {
const staticRouterContext = {}
const helmetContext = {}
const statsFile = path.resolve(__dirname, '../build', 'loadable-component.json')
const extractor = new ChunkExtractor({statsFile})
const sheets = new ServerStyleSheets()
const html = ReactDOMServer.renderToString(
sheets.collect(
<ChunkExtractorManager extractor={extractor}>
<HelmetProvider context={helmetContext}>
<StaticRouter location={req.url} context={staticRouterContext}>
<Provider store={store(preload)}>
<App />
</Provider>
</StaticRouter>
</HelmetProvider>
</ChunkExtractorManager>,
),
)
const {helmet} = helmetContext
const wholeData = template('scripts', {
chunks: html,
helmet,
extractor,
sheets,
preload,
})
res.send(wholeData)
}
const serverRenderer = (req, res, next) => {
fetchSomeExternalData()
.then(response => {
// response.data is used as preloaded data and passed to the store of redux
// also stored in a variable called __PRELOADED_STATE__ in window to use in client side
// to populate store of redux
renderPage(req, response, response.data)
})
.catch(err => {
// start server side rendering without preloaded data
renderPage(req, res)
})
}
// each url that i want to render on the server side i should add here individually
// which is not so convenient
app.get('/', serverRenderer)
app.get('/my-url-1/', serverRenderer)
app.get('/my-url-2/', serverRenderer)
app.use(express.static(path.join(__dirname, '/../build/')))
// the * doesnt seem to work
app.get('*', serverRenderer)
app.listen(PORT, () => {
if (process.send) {
process.send('ready')
}
})
for both client and server
App.js
<div>
<Header/>
<Router/>
<Footer/>
</div>
i'd be happy to hear any suggestions or solutions. thank you for your time.

Adding Truepush initalization code to Gatsby

I am using GatsbyJS. Recently I created a file in which I put this code.
import React from 'react'
import { Helmet } from 'react-helmet'
const CustomScript = () => {
return (
<Helmet>
<script type="application/javascript" src="https://sdki.truepush.com/sdk/v2.0.2/app.js" async></script>
<script>
var truepush = window.truepush || [];
truepush.push(function(){
truepush.Init({
id: "..."
},function(error){
if(error) console.error(error);
})
})
</script>
</Helmet>
)
}
export default CustomScript
But it gives me this error
ERROR #98123 WEBPACK
Generating development JavaScript bundle failed
/home/lilynicole/GitLab/portfolio/src/components/CustomScript.js
12:17 error 'truepush' is not defined no-undef
✖ 1 problem (1 error, 0 warnings)
File: src/components/CustomScript.js
failed Re-building development bundle - 5.401s
I have tried couple of things but nothing seems to work. Any help will be really appreciated.
If you want to insert inline JavaScript (the second <script> tag) with React Helmet, you'd need to quote the entire JavaScript as follows:
import React from 'react'
import { Helmet } from 'react-helmet'
const CustomScript = () => {
return (
<Helmet>
<script type="application/javascript" src="https://sdki.truepush.com/sdk/v2.0.2/app.js" async></script>
<script>{`
var truepush = window.truepush || [];
truepush.push(function(){
truepush.Init({
id: "..."
},function(error){
if(error) console.error(error);
})
})
`}
</script>
</Helmet>
)
}
export default CustomScript
The only change is that I wrapped the entire content of the <script> tag in {` ... `}.
In any case, I'd find it cleaner to write the following:
import React from 'react'
import { Helmet } from 'react-helmet'
import { useEffect } from 'react'
const CustomScript = () => {
useEffect(() => {
var truepush = window.truepush || []
truepush.push(function () {
truepush.Init(
{
id: '...',
},
function (error) {
if (error) console.error(error)
}
)
})
}, [])
return (
<Helmet>
<script
type="application/javascript"
src="https://sdki.truepush.com/sdk/v2.0.2/app.js"
async
></script>
</Helmet>
)
}
export default CustomScript
Here, instead of treating the JavaScript for the initialization as a string you're actually including it in your component code.
To make sure it runs only once you wrap it with useEffect(() => {...}, []).
(Note that I edited out your private Truepush ID and replaced it with ...; you'll need to put it back in)
I am David from Truepush Tech support. Can you please tell me if the project that you are doing is "a WebSite" or "a WebApp".
As if you are building a website then the code please try using the method that #ehrencrona has given out. That is Code by #ehrencrona
But if it is a webapp, we are not supporting them as of now and can't help you with setting it up for them.
We are working on a solution for this from our end as well. We will let you know when we have something that we can give out to help you.

react-helmet Meteor Server Side Rendering

I want to implement react-helmet on Meteor. The client side works fine and I can see the tags on the Inspect Element - but how to render it on server side for SEO? I do not understand the documentation.
ReactDOMServer.renderToString(<Handler />);
const helmet = Helmet.renderStatic();
My Code is as follows
index.html (client)
<head>
<body>
<div id="target" />
</body>
</head>
main.js (client)
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import { renderRoutes } from '../imports/startup/client/routes.jsx'
Meteor.startup(() => {
render(renderRoutes(), document.getElementById('target'));
});
I'm using React-Router to render the element to the "target"
main.js (server)
How to get the tags from the server.
import { onPageLoad } from 'meteor/server-render';
Meteor.startup(() => {
onPageLoad((sink) => {
const helmet = Helmet.renderStatic();
sink.appendToHead(helmet.meta.toString());
sink.appendToHead(helmet.title.toString());
});
});
On the about code helmet.meta.toString() returns empty. I know we need to pass something to let the helmet know the meta tags. But how to do that
Can someone help me understand what I need to write on the server to get this working? Everything except that is working fine.
Finally I found the solution :
The code on the main.js (server) should be the following
import ReactDOMServer from 'react-dom/server';
import { StaticRouter } from 'react-router-dom';
import {renderRoutes} from '../imports/startup/both/routes.jsx'
// ...
onPageLoad((sink) => {
const context = {};
const app = ReactDOMServer.renderToString(
<StaticRouter location={sink.request.url} context={context}>
{renderRoutes()}
</StaticRouter>
);
sink.renderIntoElementById('react-root', app);
const helmet = Helmet.renderStatic();
sink.appendToHead(helmet.meta.toString());
sink.appendToHead(helmet.title.toString());
});
Hope it helps someone who might run into the same issue in the future. :)

contentful api markdown conversion to HTML

Is there any simple way to convert markdown text from contentful api to render into html code to be display on html page. I have tried using pagedown and some similar techniques , but none seem to work for me .
I'm a customer success manager at Contentful -
You can check out a list of recommended parsers by language on the our FAQ.
Also, feel free to send us messages on Intercom via our UI by clicking the 'Talk to Us' link :)
I know I'm late but here's the solution using handlebars:
var marked = require('marked');
marked.setOptions({
renderer: new marked.Renderer(),
sanitize: true,
smartLists: true,
smartypants: true
});
//Home
router.get('/', (req, res) => {
client.getEntry('<ENTRY_ID>')
.then( (entry)=> {
entry.fields.body = marked(entry.fields.body);
res.render('static/index',
{
entry: entry,
user: req.user
});
}).catch( (err) => {
console.log(err);
})
});
Then in our index.hbs template we can call the markdown variable in this case (entry.fields.body) by using {{{}}} to prevent escaping.
{{{entry.fields.body}}}
Here's how I did it with React:
class NewsDetail extends React.Component {
render() {
const body = marked(this.props.body || "");
return (
<div className="news-detail">
<h2>{this.props.title}</h2>
<div dangerouslySetInnerHTML={ { __html: body } }></div>
</div>
);
}
}
The markdown content is stored in the body attribute of the NewsDetail tag (via a short function that maps contentful data structure to my app structure).
The HTML page has this script tag to pull in the marked function:
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.6/marked.min.js"></script>
I have used ReactMarkdown module: in case you also have a react app: https://github.com/rexxars/react-markdown
Example: npm install --save react-markdown
const React = require('react')
const ReactDOM = require('react-dom')
const ReactMarkdown = require('react-markdown')
const input = '# This is a header\n\nAnd this is a paragraph'
ReactDOM.render(
<ReactMarkdown source={input} />,
document.getElementById('container')
)
I am passing markdown through props and using this module inside of my child component.
I also did the same as margarita in a react app but in the child component and I pulled my markdown from contentful.
I installed react-markdown package
with
npm install react-markdown
import React from "react";
import ReactMarkdown from "react-markdown";
const AboutIntro = (props) => {
return (
<div>
<h2 className="about__intro-title">
{props.aboutTitle}
</h2>
<ReactMarkdown>
{props.aboutCopy}
</ReactMarkdown>
</div>
)
}
export default AboutIntro;
hope this helps

Resources