{"componentChunkName":"component---src-templates-post-js","path":"/i-built-a-family-meal-planner","result":{"pageContext":{"content":{"raw":"{\"nodeType\":\"document\",\"data\":{},\"content\":[{\"nodeType\":\"heading-2\",\"content\":[{\"nodeType\":\"text\",\"value\":\"What and Why\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"I watched an overview of how a dev presenter at Next.js conf 2021 built a bare bones LMS over a weekend by stitching together existing technologies. I was inspired to try my hand at using Next.js to solve a problem my family has each week: picking out meals for the week and creating a shopping list. I published the resulting code \",\"marks\":[],\"data\":{}},{\"nodeType\":\"hyperlink\",\"content\":[{\"nodeType\":\"text\",\"value\":\"here\",\"marks\":[],\"data\":{}}],\"data\":{\"uri\":\"https://github.com/hatertron3000/meal-planner\"}},{\"nodeType\":\"text\",\"value\":\". \",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"In the spirit of using existing technologies, I explored existing recipe search APIs. I found \",\"marks\":[],\"data\":{}},{\"nodeType\":\"hyperlink\",\"content\":[{\"nodeType\":\"text\",\"value\":\"Edamam recipe API\",\"marks\":[],\"data\":{}}],\"data\":{\"uri\":\"https://developer.edamam.com/edamam-recipe-api\"}},{\"nodeType\":\"text\",\"value\":\" and \",\"marks\":[],\"data\":{}},{\"nodeType\":\"hyperlink\",\"content\":[{\"nodeType\":\"text\",\"value\":\"Spoonacular API\",\"marks\":[],\"data\":{}}],\"data\":{\"uri\":\"https://spoonacular.com/api/docs/recipes-api\"}},{\"nodeType\":\"text\",\"value\":\". Based on the number of requests I could get away with on a free plan, and the fact that Spoonacular has already built a spiffy meal planner, I chose Edamam.\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"The Next.js app is hosted by \",\"marks\":[],\"data\":{}},{\"nodeType\":\"hyperlink\",\"content\":[{\"nodeType\":\"text\",\"value\":\"Vercel\",\"marks\":[],\"data\":{}}],\"data\":{\"uri\":\"https://vercel.com/\"}},{\"nodeType\":\"text\",\"value\":\" (the company behind Next.js) and the DB is hosted by MongoDB's managed DB hosting service (\",\"marks\":[],\"data\":{}},{\"nodeType\":\"hyperlink\",\"content\":[{\"nodeType\":\"text\",\"value\":\"Atlas\",\"marks\":[],\"data\":{}}],\"data\":{\"uri\":\"https://www.mongodb.com/atlas\"}},{\"nodeType\":\"text\",\"value\":\"). Both services offer a free tier that I used for this project.\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"heading-2\",\"content\":[{\"nodeType\":\"text\",\"value\":\"The Calendar\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"I decided that the user should first see a calendar with links to currently selected meals and buttons to add new meals to a selected date. I briefly evaluated a few existing calendar libraries for React, but none of them really seemed to fit my needs. To be fair, I didn't look very hard. In the end, I decided to just build the calendar from scratch. I hadn't solved the \\\"how do you programmatically make a calendar with JS\\\" problem since school, and never with React, so it was an interesting exercise.\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"I needed to populate the calendar with currently selected meals, so I exposed an API on my Next.js app (\",\"marks\":[],\"data\":{}},{\"nodeType\":\"hyperlink\",\"content\":[{\"nodeType\":\"text\",\"value\":\"which is incredibly simple to do with Next.js\",\"marks\":[],\"data\":{}}],\"data\":{\"uri\":\"https://nextjs.org/docs/api-routes/introduction\"}},{\"nodeType\":\"text\",\"value\":\") and used it to query my DB. Originally, I bootstrapped the project with \",\"marks\":[],\"data\":{}},{\"nodeType\":\"hyperlink\",\"content\":[{\"nodeType\":\"text\",\"value\":\"MongoDB's Next.js starter\",\"marks\":[],\"data\":{}}],\"data\":{\"uri\":\"https://www.mongodb.com/developer/how-to/nextjs-with-mongodb/\"}},{\"nodeType\":\"text\",\"value\":\" which fetches data using Next's \",\"marks\":[],\"data\":{}},{\"nodeType\":\"text\",\"value\":\"getServerSideProps\",\"marks\":[{\"type\":\"code\"}],\"data\":{}},{\"nodeType\":\"text\",\"value\":\" function. But, waiting for the server to respond with data before rendering the calendar wasn't quite the super-slick modern web app experience that I was trying to achieve, and relying on getServerSideProps means that the page must refresh or I need to call an API to refresh the data anyway, so I converted it to use \",\"marks\":[],\"data\":{}},{\"nodeType\":\"hyperlink\",\"content\":[{\"nodeType\":\"text\",\"value\":\"SWR\",\"marks\":[],\"data\":{}}],\"data\":{\"uri\":\"https://swr.vercel.app/\"}},{\"nodeType\":\"text\",\"value\":\" to allow the calendar to render while the data is still being fetched. You can see the resulting component \",\"marks\":[],\"data\":{}},{\"nodeType\":\"hyperlink\",\"content\":[{\"nodeType\":\"text\",\"value\":\"here\",\"marks\":[],\"data\":{}}],\"data\":{\"uri\":\"https://github.com/hatertron3000/meal-planner/blob/main/components/calendar/index.js\"}},{\"nodeType\":\"text\",\"value\":\". In the end, it looked like this:\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"embedded-asset-block\",\"content\":[],\"data\":{\"target\":{\"sys\":{\"id\":\"795Edqc939sqS1EA9XgPal\",\"type\":\"Link\",\"linkType\":\"Asset\"}}}},{\"nodeType\":\"heading-2\",\"content\":[{\"nodeType\":\"text\",\"value\":\"The Recipe Search Page\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"When the user clicks the Add/Edit Meals button on a calendar cell, the browser navigates to a dynamic route that uses the date as a path parameter (e.g. /add-meal/[dateString]). On that page, the user is able to see the currently selected meals, and search for new meals, and select new meals for that date. In this case, I did simply rely on getsServerSideProps to query the DB for existing meal selections before responding.\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"Edamam provides some decent recipe filtering options out of the box, so I put together a quick and dirty search form that implements most of their existing filters and a keyword search. Then I wired it up to another API on my Next.js app that was responsible for querying Edamam:\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"embedded-asset-block\",\"content\":[],\"data\":{\"target\":{\"sys\":{\"id\":\"6rSBps9Hg4PL2jRDEuG8nM\",\"type\":\"Link\",\"linkType\":\"Asset\"}}}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"The search results are listed below the search page in responsive grid. Pagination is handled with a \\\"Load more results\\\" button:\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"embedded-asset-block\",\"content\":[],\"data\":{\"target\":{\"sys\":{\"id\":\"1k6DV4Cur1HnXV3PbvdYWb\",\"type\":\"Link\",\"linkType\":\"Asset\"}}}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"Finally, I wired the \\\"Add to Plan\\\" button up to the meal plans API that was responsible for populating the calendar so that users can save their selected meals. That was enough for an MVP and  my wife and I set about using the app to plan the next week's meals.\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"hr\",\"content\":[],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"After a test run, I consulted with my primary users (my wife and myself) to decide which features would be most helpful. Should I build an ingredients list, a curated list of recipe source filters (I \",\"marks\":[],\"data\":{}},{\"nodeType\":\"text\",\"value\":\"love\",\"marks\":[{\"type\":\"italic\"}],\"data\":{}},{\"nodeType\":\"text\",\"value\":\" Food Network recipes, even if they are a bit unnecessarily complex at times), a nutrition analysis, or something else? We both decided that a shopping list generator would be the next most helpful feature. So I set about it.\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"heading-2\",\"content\":[{\"nodeType\":\"text\",\"value\":\"The Shopping List Generator\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"I added checkboxes on each calendar cell and a \\\"generate shopping list\\\" button beneath the calendar. When a user selects dates with recipes and clicks the button, the shopping list generator presents a list of all possible ingredients (as supplied by Edamam).\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"The generator shouldn't presume that all ingredients are necessary (we all usually have salt, pepper, and oil, right?), so the user can then click/tap an ingredient to add it to the shopping list. Once the user is satisfied with their selections, they can save the shopping list.\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"embedded-asset-block\",\"content\":[],\"data\":{\"target\":{\"sys\":{\"id\":\"c8gCmt6f23zB9UaI6DuKT\",\"type\":\"Link\",\"linkType\":\"Asset\"}}}},{\"nodeType\":\"embedded-asset-block\",\"content\":[],\"data\":{\"target\":{\"sys\":{\"id\":\"3TFYYpPWWtgGe14H02VY1B\",\"type\":\"Link\",\"linkType\":\"Asset\"}}}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"When users have saved shopping lists, they can use the Shopping Lists button on the home page to view a list of saved shopping lists, delete shopping lists, and navigate back to the shopping list generator to edit existing lists. Just like the calendar component, the shopping lists list component uses SWR for data fetching.\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"embedded-asset-block\",\"content\":[],\"data\":{\"target\":{\"sys\":{\"id\":\"3OaPvO4BCDOJQa1YFYWv3K\",\"type\":\"Link\",\"linkType\":\"Asset\"}}}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"heading-2\",\"content\":[{\"nodeType\":\"text\",\"value\":\"Results and Lessons Learned\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"After it was all said and done, I probably spent about 10 hours over the course of a few nights working on this project while the wife and baby slept. It's been a handy little tool and saved us some time looking for recipes and parsing ingredients into shopping lists. That is, it did save us time until I found that Edamam doesn't always correctly parse ingredients.\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"Some recipe results from the Edamam API are missing some if not most ingredients when compared to the actual recipe page that the result references. The Edamam support team has not responded to my request for why or how to avoid this problem, so I've started scoping a project to rebuild the app with Spoonacular... whenever I get around to it. Lesson learned: don't evaluate technology vendors based solely on what their free plan provides (duh). In the meantime, I spend a couple of extra minutes comparing the ingredients in the shopping list generator to the ingredients on the actual recipe.\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"Otherwise, this was a rewarding project to build a cool tool that my family uses to make our lives easier. I learned to use SWR, I revisited a fun logic problem (procedurally generating a calendar), and my total cost to host the app is $0.00.\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\" \",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"\",\"marks\":[],\"data\":{}}],\"data\":{}},{\"nodeType\":\"paragraph\",\"content\":[{\"nodeType\":\"text\",\"value\":\"\",\"marks\":[],\"data\":{}}],\"data\":{}}]}","references":[{"id":"eabf3e54-242b-5ff1-b2af-8840bb4ad485","contentful_id":"795Edqc939sqS1EA9XgPal","gatsbyImageData":{"images":{"sources":[{"srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/795Edqc939sqS1EA9XgPal/d7800696c577a870b2c159121949367e/meal_planner_calendar.png?w=373&h=153&q=50&fm=webp 373w,\n//images.ctfassets.net/fp1mn1nwu0hx/795Edqc939sqS1EA9XgPal/d7800696c577a870b2c159121949367e/meal_planner_calendar.png?w=746&h=306&q=50&fm=webp 746w,\n//images.ctfassets.net/fp1mn1nwu0hx/795Edqc939sqS1EA9XgPal/d7800696c577a870b2c159121949367e/meal_planner_calendar.png?w=1492&h=611&q=50&fm=webp 1492w","sizes":"(min-width: 1492px) 1492px, 100vw","type":"image/webp"}],"fallback":{"src":"//images.ctfassets.net/fp1mn1nwu0hx/795Edqc939sqS1EA9XgPal/d7800696c577a870b2c159121949367e/meal_planner_calendar.png?w=1492&h=611&q=50&fm=png","srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/795Edqc939sqS1EA9XgPal/d7800696c577a870b2c159121949367e/meal_planner_calendar.png?w=373&h=153&q=50&fm=png 373w,\n//images.ctfassets.net/fp1mn1nwu0hx/795Edqc939sqS1EA9XgPal/d7800696c577a870b2c159121949367e/meal_planner_calendar.png?w=746&h=306&q=50&fm=png 746w,\n//images.ctfassets.net/fp1mn1nwu0hx/795Edqc939sqS1EA9XgPal/d7800696c577a870b2c159121949367e/meal_planner_calendar.png?w=1492&h=611&q=50&fm=png 1492w","sizes":"(min-width: 1492px) 1492px, 100vw"}},"layout":"constrained","width":1492,"height":611}},{"id":"d13e901d-b1d8-5a57-8c87-c5cb849b2f0a","contentful_id":"6rSBps9Hg4PL2jRDEuG8nM","gatsbyImageData":{"images":{"sources":[{"srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/6rSBps9Hg4PL2jRDEuG8nM/e89903024eee76f9b6bdfc05e296f204/recipe_search.png?w=232&h=189&q=50&fm=webp 232w,\n//images.ctfassets.net/fp1mn1nwu0hx/6rSBps9Hg4PL2jRDEuG8nM/e89903024eee76f9b6bdfc05e296f204/recipe_search.png?w=463&h=377&q=50&fm=webp 463w,\n//images.ctfassets.net/fp1mn1nwu0hx/6rSBps9Hg4PL2jRDEuG8nM/e89903024eee76f9b6bdfc05e296f204/recipe_search.png?w=926&h=754&q=50&fm=webp 926w","sizes":"(min-width: 926px) 926px, 100vw","type":"image/webp"}],"fallback":{"src":"//images.ctfassets.net/fp1mn1nwu0hx/6rSBps9Hg4PL2jRDEuG8nM/e89903024eee76f9b6bdfc05e296f204/recipe_search.png?w=926&h=754&q=50&fm=png","srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/6rSBps9Hg4PL2jRDEuG8nM/e89903024eee76f9b6bdfc05e296f204/recipe_search.png?w=232&h=189&q=50&fm=png 232w,\n//images.ctfassets.net/fp1mn1nwu0hx/6rSBps9Hg4PL2jRDEuG8nM/e89903024eee76f9b6bdfc05e296f204/recipe_search.png?w=463&h=377&q=50&fm=png 463w,\n//images.ctfassets.net/fp1mn1nwu0hx/6rSBps9Hg4PL2jRDEuG8nM/e89903024eee76f9b6bdfc05e296f204/recipe_search.png?w=926&h=754&q=50&fm=png 926w","sizes":"(min-width: 926px) 926px, 100vw"}},"layout":"constrained","width":926,"height":754}},{"id":"97aced84-95bb-5687-b0e6-c62999a494a2","contentful_id":"1k6DV4Cur1HnXV3PbvdYWb","gatsbyImageData":{"images":{"sources":[{"srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/1k6DV4Cur1HnXV3PbvdYWb/6fc5c1d5a99a704a5a1e38f03f20e57d/recipe_search_results.png?w=336&h=184&q=50&fm=webp 336w,\n//images.ctfassets.net/fp1mn1nwu0hx/1k6DV4Cur1HnXV3PbvdYWb/6fc5c1d5a99a704a5a1e38f03f20e57d/recipe_search_results.png?w=672&h=369&q=50&fm=webp 672w,\n//images.ctfassets.net/fp1mn1nwu0hx/1k6DV4Cur1HnXV3PbvdYWb/6fc5c1d5a99a704a5a1e38f03f20e57d/recipe_search_results.png?w=1344&h=737&q=50&fm=webp 1344w","sizes":"(min-width: 1344px) 1344px, 100vw","type":"image/webp"}],"fallback":{"src":"//images.ctfassets.net/fp1mn1nwu0hx/1k6DV4Cur1HnXV3PbvdYWb/6fc5c1d5a99a704a5a1e38f03f20e57d/recipe_search_results.png?w=1344&h=737&q=50&fm=png","srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/1k6DV4Cur1HnXV3PbvdYWb/6fc5c1d5a99a704a5a1e38f03f20e57d/recipe_search_results.png?w=336&h=184&q=50&fm=png 336w,\n//images.ctfassets.net/fp1mn1nwu0hx/1k6DV4Cur1HnXV3PbvdYWb/6fc5c1d5a99a704a5a1e38f03f20e57d/recipe_search_results.png?w=672&h=369&q=50&fm=png 672w,\n//images.ctfassets.net/fp1mn1nwu0hx/1k6DV4Cur1HnXV3PbvdYWb/6fc5c1d5a99a704a5a1e38f03f20e57d/recipe_search_results.png?w=1344&h=737&q=50&fm=png 1344w","sizes":"(min-width: 1344px) 1344px, 100vw"}},"layout":"constrained","width":1344,"height":737}},{"id":"5eea75ad-af6c-5aeb-8195-53aa8d88fe98","contentful_id":"c8gCmt6f23zB9UaI6DuKT","gatsbyImageData":{"images":{"sources":[{"srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/c8gCmt6f23zB9UaI6DuKT/c3a136d7e9429a098b346fdd12805247/shopping-list-generator-1.png?w=367&h=193&q=50&fm=webp 367w,\n//images.ctfassets.net/fp1mn1nwu0hx/c8gCmt6f23zB9UaI6DuKT/c3a136d7e9429a098b346fdd12805247/shopping-list-generator-1.png?w=734&h=387&q=50&fm=webp 734w,\n//images.ctfassets.net/fp1mn1nwu0hx/c8gCmt6f23zB9UaI6DuKT/c3a136d7e9429a098b346fdd12805247/shopping-list-generator-1.png?w=1467&h=773&q=50&fm=webp 1467w","sizes":"(min-width: 1467px) 1467px, 100vw","type":"image/webp"}],"fallback":{"src":"//images.ctfassets.net/fp1mn1nwu0hx/c8gCmt6f23zB9UaI6DuKT/c3a136d7e9429a098b346fdd12805247/shopping-list-generator-1.png?w=1467&h=773&q=50&fm=png","srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/c8gCmt6f23zB9UaI6DuKT/c3a136d7e9429a098b346fdd12805247/shopping-list-generator-1.png?w=367&h=193&q=50&fm=png 367w,\n//images.ctfassets.net/fp1mn1nwu0hx/c8gCmt6f23zB9UaI6DuKT/c3a136d7e9429a098b346fdd12805247/shopping-list-generator-1.png?w=734&h=387&q=50&fm=png 734w,\n//images.ctfassets.net/fp1mn1nwu0hx/c8gCmt6f23zB9UaI6DuKT/c3a136d7e9429a098b346fdd12805247/shopping-list-generator-1.png?w=1467&h=773&q=50&fm=png 1467w","sizes":"(min-width: 1467px) 1467px, 100vw"}},"layout":"constrained","width":1467,"height":773}},{"id":"c87a032d-60fa-5212-aa6a-4e9a750c7831","contentful_id":"3TFYYpPWWtgGe14H02VY1B","gatsbyImageData":{"images":{"sources":[{"srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/3TFYYpPWWtgGe14H02VY1B/17e389f12c28953cdb0b8df12614a3ca/shopping-list-generator-2.png?w=367&h=189&q=50&fm=webp 367w,\n//images.ctfassets.net/fp1mn1nwu0hx/3TFYYpPWWtgGe14H02VY1B/17e389f12c28953cdb0b8df12614a3ca/shopping-list-generator-2.png?w=734&h=378&q=50&fm=webp 734w,\n//images.ctfassets.net/fp1mn1nwu0hx/3TFYYpPWWtgGe14H02VY1B/17e389f12c28953cdb0b8df12614a3ca/shopping-list-generator-2.png?w=1468&h=756&q=50&fm=webp 1468w","sizes":"(min-width: 1468px) 1468px, 100vw","type":"image/webp"}],"fallback":{"src":"//images.ctfassets.net/fp1mn1nwu0hx/3TFYYpPWWtgGe14H02VY1B/17e389f12c28953cdb0b8df12614a3ca/shopping-list-generator-2.png?w=1468&h=756&q=50&fm=png","srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/3TFYYpPWWtgGe14H02VY1B/17e389f12c28953cdb0b8df12614a3ca/shopping-list-generator-2.png?w=367&h=189&q=50&fm=png 367w,\n//images.ctfassets.net/fp1mn1nwu0hx/3TFYYpPWWtgGe14H02VY1B/17e389f12c28953cdb0b8df12614a3ca/shopping-list-generator-2.png?w=734&h=378&q=50&fm=png 734w,\n//images.ctfassets.net/fp1mn1nwu0hx/3TFYYpPWWtgGe14H02VY1B/17e389f12c28953cdb0b8df12614a3ca/shopping-list-generator-2.png?w=1468&h=756&q=50&fm=png 1468w","sizes":"(min-width: 1468px) 1468px, 100vw"}},"layout":"constrained","width":1468,"height":756}},{"id":"9d58f992-0f7f-54bd-9197-e86c79659c6f","contentful_id":"3OaPvO4BCDOJQa1YFYWv3K","gatsbyImageData":{"images":{"sources":[{"srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/3OaPvO4BCDOJQa1YFYWv3K/020c6a68ab579dce8eb5fbac5ee8f159/shopping-lists-list.png?w=162&h=84&q=50&fm=webp 162w,\n//images.ctfassets.net/fp1mn1nwu0hx/3OaPvO4BCDOJQa1YFYWv3K/020c6a68ab579dce8eb5fbac5ee8f159/shopping-lists-list.png?w=325&h=169&q=50&fm=webp 325w,\n//images.ctfassets.net/fp1mn1nwu0hx/3OaPvO4BCDOJQa1YFYWv3K/020c6a68ab579dce8eb5fbac5ee8f159/shopping-lists-list.png?w=649&h=337&q=50&fm=webp 649w","sizes":"(min-width: 649px) 649px, 100vw","type":"image/webp"}],"fallback":{"src":"//images.ctfassets.net/fp1mn1nwu0hx/3OaPvO4BCDOJQa1YFYWv3K/020c6a68ab579dce8eb5fbac5ee8f159/shopping-lists-list.png?w=649&h=337&q=50&fm=png","srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/3OaPvO4BCDOJQa1YFYWv3K/020c6a68ab579dce8eb5fbac5ee8f159/shopping-lists-list.png?w=162&h=84&q=50&fm=png 162w,\n//images.ctfassets.net/fp1mn1nwu0hx/3OaPvO4BCDOJQa1YFYWv3K/020c6a68ab579dce8eb5fbac5ee8f159/shopping-lists-list.png?w=325&h=169&q=50&fm=png 325w,\n//images.ctfassets.net/fp1mn1nwu0hx/3OaPvO4BCDOJQa1YFYWv3K/020c6a68ab579dce8eb5fbac5ee8f159/shopping-lists-list.png?w=649&h=337&q=50&fm=png 649w","sizes":"(min-width: 649px) 649px, 100vw"}},"layout":"constrained","width":649,"height":337}}]},"headline":"I built a family meal planner","summary":{"summary":"I built a meal planner and shopping list generator with Next.js, MongoDB and Edamam recipe search."},"publishedDate":"2021-11-16T20:00-06:00","slug":"i-built-a-family-meal-planner","thumbnail":{"gatsbyImageData":{"images":{"sources":[{"srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/7Ghu3UsJnCIvLz5K37B8jv/30c0e5be1e3ccaac19421cfe1c6e14ee/broccoli.png?w=127&h=127&q=50&fm=webp 127w,\n//images.ctfassets.net/fp1mn1nwu0hx/7Ghu3UsJnCIvLz5K37B8jv/30c0e5be1e3ccaac19421cfe1c6e14ee/broccoli.png?w=255&h=254&q=50&fm=webp 255w,\n//images.ctfassets.net/fp1mn1nwu0hx/7Ghu3UsJnCIvLz5K37B8jv/30c0e5be1e3ccaac19421cfe1c6e14ee/broccoli.png?w=509&h=507&q=50&fm=webp 509w","sizes":"(min-width: 509px) 509px, 100vw","type":"image/webp"}],"fallback":{"src":"//images.ctfassets.net/fp1mn1nwu0hx/7Ghu3UsJnCIvLz5K37B8jv/30c0e5be1e3ccaac19421cfe1c6e14ee/broccoli.png?w=509&h=507&q=50&fm=png","srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/7Ghu3UsJnCIvLz5K37B8jv/30c0e5be1e3ccaac19421cfe1c6e14ee/broccoli.png?w=127&h=127&q=50&fm=png 127w,\n//images.ctfassets.net/fp1mn1nwu0hx/7Ghu3UsJnCIvLz5K37B8jv/30c0e5be1e3ccaac19421cfe1c6e14ee/broccoli.png?w=255&h=254&q=50&fm=png 255w,\n//images.ctfassets.net/fp1mn1nwu0hx/7Ghu3UsJnCIvLz5K37B8jv/30c0e5be1e3ccaac19421cfe1c6e14ee/broccoli.png?w=509&h=507&q=50&fm=png 509w","sizes":"(min-width: 509px) 509px, 100vw"}},"layout":"constrained","width":509,"height":507},"title":"Broccoli","fixed":{"src":"//images.ctfassets.net/fp1mn1nwu0hx/7Ghu3UsJnCIvLz5K37B8jv/30c0e5be1e3ccaac19421cfe1c6e14ee/broccoli.png?w=250&q=50","srcSet":"//images.ctfassets.net/fp1mn1nwu0hx/7Ghu3UsJnCIvLz5K37B8jv/30c0e5be1e3ccaac19421cfe1c6e14ee/broccoli.png?w=250&h=249&q=50 1x,\n//images.ctfassets.net/fp1mn1nwu0hx/7Ghu3UsJnCIvLz5K37B8jv/30c0e5be1e3ccaac19421cfe1c6e14ee/broccoli.png?w=375&h=374&q=50 1.5x,\n//images.ctfassets.net/fp1mn1nwu0hx/7Ghu3UsJnCIvLz5K37B8jv/30c0e5be1e3ccaac19421cfe1c6e14ee/broccoli.png?w=500&h=498&q=50 2x","width":250,"height":249}}}},"staticQueryHashes":["3159585216"]}