I'm in 💖 with the JAMStack, It gets the work done. One of the very exciting companies in this area is Netlify. Anyone who tested their hosting would tell you it's top class and I would recommend it anyday.
In this post we will explore using their Serverless functions with create-react-app.
The What.
What we intend to create a reading application. You give it the URL and allows you to view the simplified content fit for reading.
The How
We ideally to parse the url string from the backend to avoid getting blocked by CORS. We will use a Netlify Function to achieve this. We will use Postlight's Mercury Parser with the function to parse the simplified version from URL.
The Detail
First let's create a new React application with create-react-app:
npm init react-app the-reader
The Build step
Now, to setup Netlify functions, create a top level folder, I'm naming it functions
. We have to update the build step so that the function is also build when we run yarn build
.
Netlify has published a package netlify-lambda to help with the build:
yarn add netlify-lambda npm-run-all --dev
npm-run-all
is used to run both tasks in parallel. In package.json
:
"scripts": {
"build": "run-p build:**",
"build:app": "react-scripts build",
"build:lambda": "netlify-lambda build functions/",
}
Create a netlify.toml
so that netlify
knows where the build is:
[build]
command = "yarn build"
functions = "build-lambda" # netlify-lambda gets build to this folder
publish = "build" # create-react-app builds to this folder
Remember to add
build-lambda
to.gitignore
.
Create your first function by creating a JS file in functions
folder we created earlier.
Netlify can recognise JS files in the folder or in nested folders.
In functions/parse.js
:
export async function handler(event) {
return {
statusCode: 200,
body: JSON.stringify({ data: "hello world" })
}
}
Dummy function
From the frontend application you can now use fetch
to query .netlify/functions/parse.js
(your folder structure prepended with .netlify/
) to get the dummy response we put in. But with a twist, it works only when you deploy the application to Netlify. That's not a good development methodology. This is because the functions are not build yet and there is .netlify/
path to get the data from.
netlify-lambda
has a serve mode for development, so that the functions can be build for any changes and updated to a server.
Add the following to package.json
and keep it running in the background with npm start
:
"scripts": {
"serve:lambda": "netlify-lambda serve functions/",
},
The Proxy
You will find that the functions is now running on a server with localhost:9000
. But even if you could add an environment variable to query this server, there is an issue with CORS now. Your frontend and functions are running on different servers. To get around this, you can add a proxy with create-react-app
. You can find complete instructions in the docs.
What we have to do is to add src/setupProxy.js
, you don't have to import this file anywhere, just create, add code and ✨ restart your development server.
const proxy = require("http-proxy-middleware");
module.exports = function(app) {
app.use(
proxy("/.netlify/functions/", {
target: "http://localhost:9000/",
pathRewrite: {
"^/\\.netlify/functions": "",
},
})
);
};
What this is essentially doing is to rewrite any API calls to .netlify/functions
to localhost:9000
and get response from there. This only works in development, so it works without the server in production.
The API call
First, let's setup a form where user can enter a URL and request the server.
import React from "react";
const App = () => {
const handleSubmit = () => {};
return (
<main>
<form onSubmit={handleSubmit}>
<input type="url" placeholder="Enter url here" name="url" label="url" />
<button>View</button>
</form>
</main>
)
}
Filling in the handleSubmit
function:
import { stringify } from "qs"; // for encoding the URL as a GET parameter
const handleSubmit = (event) => {
event.preventDefault();
const url = event.target.url.value;
fetch(
`/.netlify/functions/parse?${stringify({ q: reqUrl })}`
).then(response => response.json())
}
If you run this function now, it will return the { data: "Hello world" }
we added earlier (hopefully).
To return some real data, let's modify the functions/parse.js
to:
import Mercury from "@postlight/mercury-parser";
export async function handler(event) {
const parameters = event.queryStringParameters;
const url = parameters.q;
if (!url) {
return {
statusCode: 400,
body: JSON.stringify({ error: "Invalid/No URL provided" }),
};
}
try {
const response = await Mercury.parse(url);
return {
statusCode: 200,
body: JSON.stringify({ data: response }),
};
} catch (err) {
return {
statusCode: 500,
body: JSON.stringify({ error: err }),
};
}
}
The function takes URL as an argument through queryStringParameters
and uses Mercury.parse
to get the simplified version and return it to the user.
Now, running the frontend would get you the real response from the serverless function (which underwhelmingly has a server now, but you can always push and get it deployed).
Some changes on the frontend to display the data from backend:
import React, { useState } from "react";
import { stringify } from "qs";
const App = () => {
const [ result, setResult ] = useState(null);
const handleSubmit = (event) => {
event.preventDefault();
const url = event.target.url.value;
fetch(
`/.netlify/functions/parse?${stringify({ q: reqUrl })}`
)
.then(response => response.json())
.then(jsonResponse => setResult(jsonResponse.data));
}
return (
<main>
<form onSubmit={handleSubmit}>
<input type="url" placeholder="Enter url here" name="url" label="url" />
<button>View</button>
</form>
{result && (
<article dangerouslySetInnerHTML={{ __html: data.content }} />
)}
</main>
)
}
and we are Done 🥂.
To convert this to a PWA, you can very simply add the service workers on the create-react-app
and adjust the parameters in manifest.json
.
Bonus, you can set the app as a share target to share URLs directly into your PWA.
You can find the complete code in the following repository:
agneym / the-reader
A JAM Stack Reading Mode PWA with React & Netlify Functions
The Reader App
This project was bootstrapped with Create React App.
- Icons from Feathers Project
- Styling by Styled Components
- Reading Mode Parser by Mercury - Postlight
- Serverless Lambda with Netlify Functions
- Deployment with Netlify Hosting
Contributing
Development
yarn
yarn start # start the frontend development server
yarn serve:lambda # start the functions development server
Build
yarn
yarn build
Originally written on my blog
Top comments (0)