A JAMStack Blog in No Time with Next.js & Bison
Next.js is a fantastic framework for building fast and powerful JAMStack web applications — that’s one reason among many that it’s our go-to JavaScript framework at Echobind. We’ve wrapped Next.js with a set of tools we regularly use into a JAMStack generator called Bison, which makes Next.js even more powerful. In this article, I’ll show you the steps to building a custom blog with Bison and Next.js.
If you want to jump right to the code, here’s the repo on GitHub.
Run the Bison Generator
yarn create bison-app bison-blog
This will generate your app skeleton, run yarn install
and create a Git repo automatically.
Set up your database
You’ll want to check the latest README for full steps, but it’s as simple as making sure PostgresSQL is running (I highly recommend Postgres.app) and running yarn db:setup
.
You’ll be asked a few questions during setup, and it’ll do the rest of the work for you.
Start the app
Run yarn dev
from the directory you just created, and visit http://localhost:3000. Your app is running, and you should see a screen like the one below. We’re in business.
Create a user account
Bison comes pre-packaged with a simple user authentication mechanism, so signing in or signing up is a cinch.
First, tap Login to get to the login screen, then click Sign Up.
Fill out the form and submit, and you’ll be logged in and redirected to the home page.
Add some database tables
We’ll need a Posts
table and model to create and list posts. Edit schema.prisma
which is the source of truth for the data model. Adding a Post
model is pretty easy.
Then run yarn prisma migrate save --experimental
, to generate a database migration for it.
Now run the migration using yarn db:migrate
.
If you look at your database using Postico or psql
, you’ll see the resulting table and columns.
Generate a page for creating posts
Use Bison’s page generator to stub the /posts/new
page:
yarn g:page posts/new
Generate components
We’ll need 2 React components for creating and showing posts PostList
and PostForm
, so we’ll generate them with Bison’s CLI commands. These wrap 👍 some pre-packaged Hygen generators that Bison comes with.
yarn g:component PostList yarn g:component PostForm
Now you’ll see these 4 new files in your folder tree:
All rightie! Let’s add some code.
Create the “New Post” form
The page container for /posts/new.tsx
is quite simple; aside from styling, we add the component which we’ll build immediately after.
import React from 'react'; import Head from 'next/head'; import { Heading, Flex } from '@chakra-ui/core'; import { PostForm } from '../../components/PostForm'; function PostsNewPage() { return ( <> <Head> <title>PostsNewPage</title> </Head> <Flex direction="column" justify="center"> <Heading size="lg">New Post</Heading> <PostForm /> </Flex> </> ); } export default PostsNewPage;
Create the component
Full code is here. Here are some highlights.
Bison comes packaged with react-hook-form, so we build the form out like this:
<form onSubmit={handleSubmit(onSubmit)}> <FormControl isInvalid={errors.title}> <FormLabel htmlFor="title">Post Title</FormLabel> <Input type="text" name="title" id="title" ref={register({ required: true })} /> <FormErrorMessage>{errors.title && <span>This field is required</span>}</FormErrorMessage> </FormControl> <FormControl isInvalid={errors.body}> <FormLabel htmlFor="body">Post Body</FormLabel> <Textarea name="body" id="body" ref={register({ required: true })} /> <FormErrorMessage>{errors.body && <span>This field is required</span>}</FormErrorMessage> </FormControl> <Button type="submit">Submit</Button> </form>
Because Bison also sets up nexus-plugin-prisma for us, We add a mutation to create a post like so:
export const CREATE_POST_MUTATION = gql` mutation CreatePost($data: PostCreateInput!) { createOnePost(data: $data) { id title body } } `;
In turn, Bison’s graphql-codegen
configuration sees the above and generates a nice React hook for us to plug into 🎉:
import { useCreatePostMutation } from '../types';
We use this and a few other hooks to get our data into the form component:
export function PostForm() { const { register, handleSubmit, errors } = useForm(); const [createPost] = useCreatePostMutation(); const router = useRouter(); const { user: { id: userId } = {} } = useAuth(); // ... }
And here’s the onSubmit
handler which triggers the createPost mutation and redirects back to the homepage upon success:
const onSubmit = async (data) => { // Create the post await createPost({ variables: { data: { ...data, author: { connect: { id: userId, }, }, }, }, }); // Redirect to homepage await router.replace('/'); };
Now we’ve got a form that saves a post to the database. Voila. 🚀
Create the component
Now that we’ve got data, let’s display it.
If you remember, we already ran the generator for the component. So we start by importing the
into the homepage and displaying it like so:
Now we’ll need to fetch and display the posts. When fetching data with Bison, we recommend something called a “cell.” A cell is a concept borrowed from Redwood.js. It’s a declarative way of wrapping the GraphQL query together with loading, success, and error states.
yarn g:cell FetchPosts
will create /cells/FetchPosts.tsx
as below.
Now we’ll fill the cell out with the correct query and some formatting for the rendered posts.
Full code here. Important snips:
export const POSTS_QUERY = gql` query posts { posts { id title body } } `; export const Success = ({ posts }: PostsQuery) => { return ( <Stack> {posts.map((p) => ( <Box p={5} shadow="md" key={p.id}> <Heading>{p.title}</Heading> {p.body} </Box> ))} </Stack> ); };
Don’t forget to include the cell in the component:
export function PostList() { return ; }
And we’re done. You now have a (very simple and basic but functioning) blog built in Next.js. Add some data and start having fun.
Conclusion
Bison is in its early stages, but is already a useful tool for kickstarting your Next.js project quickly with great stuff like Prisma, Nexus, and Hygen built-in.
If you try out this tutorial, please drop me a line with any feedback or questions. Or comment right here.