GitHub ReadME in NextJS - Kevin Uriel Fonseca
GitHub Account Setup and Env Variables in NextJS
First thing first, to get the readME file we need access to our GitHub personal account settings and will need to generate a new personal token. Once that is done, copy the token and create a document called – next.config.js – if you have not done already; in the same document name a variable and paste the token next to it:
module.exports = { publicRuntimeConfig: { GITHUB_PERSONAL_TOKEN: 'YOUR_PERSONAL_TOKEN_GOES_HERE', }, reactStrictMode: true,}
Make sure to install these dependencies in your NextJS root folder, please!
npm i axios showdown base64
Let’s Fetch the readMe file
Let’s grab the single article code from one of my previous post.
import { withRouter } from 'next/router'// ACTIONS// HELPERSimport Row from 'react-bootstrap/Row'import { getWordPressPost } from '@/actions/wordpress'import Content from '@/layout/Container'import Sidebar from '@/layout/Sidebar'export const getServerSideProps = async (context) => { const params = `${context.query.id}` const postId = `?post=${context.query.id}` const wordPressPost = (await getWordPressPost(params)()) || null return { props: { params: params, serverWordPressPost: wordPressPost, }, }}const FetchHtml = ({ text = `` }) => { return ( text && ( <div className="fetchHtml" dangerouslySetInnerHTML={{ __html: text.replace(/\n\r?/g, ''), }} ></div> ) )}const SingleBlog = ({ params, serverWordPressPost, router }) => { return ( <div className="container mt-3"> <Row> <Content> <FetchHtml text={serverWordPressPost.content.rendered} /> </Content> <Sidebar>SIDEBAR</Sidebar> </Row> </div> )}export default withRouter(SingleBlog)
The code above is enough just to fetch the data from the WordPress API but right now what we need is to fetch data from the GitHub API also, especifically, the readME file. The API endpoint needed for this task looks as follow https://api.github.com/repos/{owner}/{repository}/readme
with some headers included also. Take a look into my action file:
export const getWordPressThemeReadMe = (repoName) => async () => { try { const res = await axios.get( `https://api.github.com/repos/kirasiris/${repoName}/readme`, { // As per GitHub recommendation 'Setting to application/vnd.github.v3+json is recommended.' accept: 'application/vnd.github.v3+json', headers: { Authorization: { GITHUB_TOKEN }, }, } ) return res.data } catch (err) { // const error = err.response.data.message; const error = err?.response?.data?.error?.errors const errors = err?.response?.data?.errors if (error) { // dispatch(setAlert(error, 'danger')); error && Object.entries(error).map(([, value]) => console.error(value.message)) } if (errors) { errors.forEach((error) => console.error(error.msg)) } console.error(err?.response?.statusText) return { msg: err?.response?.statusText, status: err?.response?.status } }}
There are two additional headers, the accept and the Authorization(not needed but highly recommended) ones which will enable us to perform unlimited API calls to GitHub!.
String Manipulation?; A Function!
The GitHub API returns encoded data so we also need to decode it in order for us to read its content. The libraries that will help us with this are showdown and base-64. You should have installed these two in the beginning of the post.
We are going to end up creating a function but instead of just having it available in a single document, lets make it dynamic, that means, a function that can be accessed by others documents when needed. Let’s call it readMeDecoder:
import showdown from 'showdown'import base64 from 'base-64'export const readMEDecoder = (text) => { const converter = new showdown.Converter() const readMEContentBase64 = base64.decode(text) const textConverted = converter.makeHtml(readMEContentBase64) return textConverted}
The code is self-explanatory. We declare a showdown converter class then we proceed to decode the markdown data and we finish by converting it into HTML. However, we still need to return the new string as seen in the last line of code.
Almost There!
Go to our single article code and import the readMEDecoder function from whatever location you have it. Access to the getServerSideProps function and call the function we created, getWordPressThemeReadMe. Furthermore, we also need to take a look into this code below
const params = `${context.query.id}`const wordPressPost = (await getWordPressPost(params)()) || null
The code above is what fetches the corresponding data according to the id coming from the URL; then if nothing is found, we simply make the variable to be equal to null. This variable, wordPressPost
, will help us to fetch the readMe content.
Our Last Step
You can use whatever you want to the getWordPressThemeReadMe
function but I highly recommend you to build a custom field solely for this function. I’m currently using the title of the posts that I have made so far as the string passed to the function but there’s a bit of a mistake that I was not aware of.
The con against using the title as the string parameter is that it needs to match the name of the repository. Per example, befree is not equal to be-free. Make sure to play with this as much as you want!.
const params = `${context.query.id}`const wordPressPost = (await getWordPressPost(params)()) || nullconst wordPressThemeReadMe = (await getWordPressThemeReadMe( wordPressPost.title.rendered.toLowerCase().replace(/\s/gm, '') )()) || null/* * * README * */const readMEContent = readMEDecoder( wordPressThemeReadMe.content ? wordPressThemeReadMe.content : 'Tm8gcmVhZE1FIGZpbGU=')return { props: { params: params, serverWordPressTheme: wordPressPost, serverWordPressThemeReadMe: readMEContent, },}
Take an deep look into the readMEContent variable as this one is passing the readMe content from the API call but if nothing is found, well, then simply just fetch a different string, Tm8gcmVhZE1FIGZpbGU=
which equals to No readME file. Moreover, we need this available to our users. Check the output of this code by clicking here.
{ serverWordPressThemeReadMe && ( <> <hr /> <FetchHtml text={serverWordPressThemeReadMe} /> </> )}
Bye Bye 🙂