{"id":19522171,"url":"https://github.com/captaincodeman/appengine-ssr","last_synced_at":"2025-04-26T09:32:06.134Z","repository":{"id":44104125,"uuid":"138243868","full_name":"CaptainCodeman/appengine-ssr","owner":"CaptainCodeman","description":"Puppeteer Middleware for Server Side Rendering from AppEngine Go ","archived":false,"fork":false,"pushed_at":"2018-10-22T16:52:45.000Z","size":1043,"stargazers_count":13,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-04T10:34:09.285Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CaptainCodeman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-06-22T02:16:48.000Z","updated_at":"2023-08-25T09:58:38.000Z","dependencies_parsed_at":"2022-09-21T09:54:23.819Z","dependency_job_id":null,"html_url":"https://github.com/CaptainCodeman/appengine-ssr","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CaptainCodeman%2Fappengine-ssr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CaptainCodeman%2Fappengine-ssr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CaptainCodeman%2Fappengine-ssr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CaptainCodeman%2Fappengine-ssr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CaptainCodeman","download_url":"https://codeload.github.com/CaptainCodeman/appengine-ssr/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250967223,"owners_count":21515563,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-11T00:37:30.177Z","updated_at":"2025-04-26T09:32:05.101Z","avatar_url":"https://github.com/CaptainCodeman.png","language":"Go","readme":"# appengine-ssr\n\nHttp middleware for AppEngine to selectively Server-Side\nRender a page and optionally caching the results. Designed\nto work with Puppeteer.\n\n## Why SSR\n\nSay you have the latest and greatest front-end web-client\ntechnology for your new site. All your users have one of\nthe latest evergreen browsers that support the new platform\nfeatures you make use of so everything is good ...right?\n\nUnfortunately, Googlebot is based on a much older version \nof Chrome that doesn't support these new features so it \nprobably doesn't understand any of it. Oh dear. ShadowDOM?\nWebComponents? ES6? What are they?\n\nHere's how your sweet UI appears to Googlebot (using the\n\"Fetch as Google\" feature in Webmaster Tools):\n![Fetch as Google with ShadowDom](https://raw.githubusercontent.com/captaincodeman/appengine-ssr/master/examples/fag-shadow.png)\n\nYeah, your SEO isn't even SE happy, let alone Optimized ...\n\nNot only is it bad for Search Engine traffic but any web\nrequests that expect to see meta-data in the page source\nwill be disappointed which means rich embedding of content\ncards within sites such as Facebook and Twitter won't work\neither. There are more users of your site than humans with\nweb-browsers.\n\nOne solution is to use Server-Side Rendering and there have\nbeen a number of services that offer this but they can be\nexpensive especially if you have a large site with lots of\nchanging content. Some client frameworks also provide options\nfor Server-Side Rendering but they add complexity if they \nwork at all.\n\nFortunately, there's now the option of using a \"headless\"\nChrome browser via Puppeteer which is what this middleware\nis intended to help with. It means when the Googlebot (or \nany other bot you configure) requests your site it can be\nconverted into ye-olde HTML like a static site. Now your\nsite can be rendered and understood. SEO glory awaits!\n\n![Fetch as Google with ShadyDOM](https://raw.githubusercontent.com/captaincodeman/appengine-ssr/master/examples/fag-ssr.png)\n\n## How it works\n\nThe middlware checks if the request is coming from any of\nthe configured bots by parsing the User-Agent string of the\nrequest. If it isn't, it's processed as normal and the package\ndoes nothing.\n\nIf it _is_ a bot request, the middleware first checks if the\nURL has already been server-side rendered and cached (because\nit can be a bit expensive to do you don't want to do it every\ntime) and will serve that instead. The caching is configurable\nand can be turned off. If there is no cached version then the\nmiddleware calls the Puppeteer service to render the page but\nadds an extra `headless` QueryString parameter to the URL when\ndoing so.\n\nThe page should use this `headless` parameter to configure\nrendering using `ShadyDOM` (otherwise the Chrome version used\nby Puppeteer will render the exact same version the app would\nhave sent for the request anyway). Enabling ShadyDOM means the\ncontent will be rendered as a regular DOM tree and any dynamic\ncontent that relies on JavaScript to render will be included \nwith the JavaScript then removed. The result is pure HTML \u0026amp;\nCSS content which can be understood by any bot or other client.\n\nThe middleware checks requests for the `headless` parameter\nso it can prevent any 'render-loop' and you can also pass in\nanother configurable QueryString option (the default is `ssr`)\nto force Server Side Rendering which is useful while testing.\n\n## Demo\n\nA typical client-side rendered page is available at:\n[http://ssr-dot-captain-codeman.appspot.com/](http://ssr-dot-captain-codeman.appspot.com/)\n\nHere's how the DOM looks when rendered in a modern browser\nsuch as Chrome. Note the shadow-root:\n\n![ShadowDOM rendered](https://raw.githubusercontent.com/captaincodeman/appengine-ssr/master/examples/dom-shadow.png)\n\nTo view the page as it would look when Server-Side Rendered\nyou can override the User-Agent parsing by adding `?ssr` to\nthe URL:\n\n[http://ssr-dot-captain-codeman.appspot.com/?ssr](http://ssr-dot-captain-codeman.appspot.com/?ssr)\n\nNow the content is rendered as a regular DOM tree which\ncan be understood by any bot. If you generate metadata in\nyour front-end framework, this will enable services such as\nFacebook and Twitter to render it:\n\n![Server Side Rendered](https://raw.githubusercontent.com/captaincodeman/appengine-ssr/master/examples/dom-ssr.png)\n\n## Installation\n\nInstall using `go get`\n\n    go get -i github.com/captaincodeman/appengine-ssr\n\n## Usage\n\nCreate an instance of the SSR middleware and add it to your\nRouter package of choice (or wrap the standard http package \nMux). It uses another [Appengine Context Middleware Package](\"github.com/captaincodeman/appengine-context\")\nso needs to be added high in the middleware chain before any\nother changes the request. If you use that package to access\nAppEngine Services you don't need to add the middleware for\nit. Having the middleware early in the request lifecyle makes\nsense because the request isn't going to be handled directly\nanyway.\n\nExample:\n\n```go\npackage main\n\nimport (\n\t\"time\"\n\n\t\"net/http\"\n\n\t\"google.golang.org/appengine\"\n\n\t\"github.com/captaincodeman/appengine-ssr\"\n)\n\nfunc main() {\n\tmw := ssr.NewSSR(\"https://pptraas.com\")\n\n\thandler := http.HandlerFunc(handle)\n\thttp.Handle(\"/\", mw.Middleware(handler))\n\tappengine.Main()\n}\n\nfunc handle(w http.ResponseWriter, r *http.Request) {\n\thttp.ServeFile(w, r, \"index.html\")\n}\n```\n\nThe only mandatory configuration setting is the address\nof the Puppeteer rendering service. The example above\nuses the [Puppeteer as a Service](https://pptraas.com)\nexample but you should really run your own instance to\nguarantee performance - I'll add an example for creating\none using Google Compute Engine Docker Optimized instances\nwhich can auto-scale and ensure availability.\n\n## Configuration Options\n\nOther configuration options can be added to the constructor\nfor example, if you want a shorter cache expiration and have\nlogging enabled:\n\n```go\nmw := ssr.NewSSR(\"https://pptraas.com\",\n    ssr.Expiration(time.Minute*10),\n    ssr.Verbose(true),\n)\n```\n\nOptions available:\n\n`ssr.UserAgents(userAgents []string)`\n\nSet the User-Agents that will be Server Side Rendered. The\ndefault list is:\n\n```go\nvar defaultUserAgents = []string{\n\t\"W3C_Validator\",\n\t\"baiduspider\",\n\t\"bingbot\",\n\t\"facebookexternalhit\",\n\t\"LinkedInBot\",\n\t\"Pinterest\",\n\t\"Slackbot-LinkExpanding\",\n\t\"TwitterBot\",\n\t\"Googlebot\",\n\t\"Mediapartners-Google\",\n}\n```\n\n`ssr.UserAgentParser(parser *uaparser.Parser)`\n\nSet the [User-Agent Parser](github.com/ua-parser/uap-go/uaparser)\nto use if, for instance, you didn't want to use the built-in\nUser-Agent string definitions.\n\n`ssr.Verbose(verbose bool)`\n\nSet to true to output additional information to the AppEngine\nlogs (Stackdriver).\n\n`ssr.Timeout(timeout time.Duration)`\n\nTimeout for request to Puppeteer service (default 30 seconds).\n\n`ssr.NoCache()`\n\nDisables caching of Server-Side Rendered content.\n\n`ssr.Memcache(prefix string)`\n\nCache Server-Side Rendered content using the built-in\nAppEngine Memcache service (default). A prefix string will \nbe added to the cache key to avoid collisions with any other \ncaching your app may be performing.\n\n`ssr.Expiration(expiration time.Duration)`\n\nSet the cache expiration (default 1 hour). This will save\nrepeat bot-requests within that time from being re-rendered\n(as long as the items aren't evicted from the cache).\n\n`ssr.HeadlessParam(name string)`\n\nSet the name of the querystring parameter used to indicate \na 'headless' request (default `headless`).\n\n`ssr.OverrideParam(name string)`\n\nSet the name of the querystring parameter used to override \nSSR rendering (default `ssr`). Set to empty to disable the\noption.\n\n## Future Work\n\nThis could probably be made more generic by having all the\nAppEngine specific pieces be separate configurable services\nas the Memcache option already is and other pieces set to be\nconditionally compuled. But my current requirements only\nneed it to work with AppEngine so that's what it does ... \nfor now.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcaptaincodeman%2Fappengine-ssr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcaptaincodeman%2Fappengine-ssr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcaptaincodeman%2Fappengine-ssr/lists"}