Skip to content

Instantly share code, notes, and snippets.

@thoop
Last active May 27, 2025 07:08
Show Gist options
  • Save thoop/8165802 to your computer and use it in GitHub Desktop.
Save thoop/8165802 to your computer and use it in GitHub Desktop.
Official prerender.io nginx.conf for nginx
# Change YOUR_TOKEN to your prerender token
# Change example.com (server_name) to your website url
# Change /path/to/your/root to the correct value
server {
listen 80;
server_name example.com;
root /path/to/your/root;
index index.html;
location / {
try_files $uri @prerender;
}
location @prerender {
proxy_set_header X-Prerender-Token YOUR_TOKEN;
set $prerender 0;
if ($http_user_agent ~* "googlebot|bingbot|yandex|baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest\/0\.|pinterestbot|slackbot|vkShare|W3C_Validator|whatsapp") {
set $prerender 1;
}
if ($args ~ "_escaped_fragment_") {
set $prerender 1;
}
if ($http_user_agent ~ "Prerender") {
set $prerender 0;
}
if ($uri ~* "\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff|svg|eot)") {
set $prerender 0;
}
#resolve using Google's DNS server to force DNS resolution and prevent caching of IPs
resolver 8.8.8.8;
if ($prerender = 1) {
#setting prerender as a variable forces DNS resolution since nginx caches IPs and doesnt play well with load balancing
set $prerender "service.prerender.io";
rewrite .* /$scheme://$host$request_uri? break;
proxy_pass http://$prerender;
}
if ($prerender = 0) {
rewrite .* /index.html break;
}
}
}
@viperfx
Copy link

viperfx commented Aug 1, 2021

Has anyone been able to figure out how to handle relative URLs with nginx? I am serving a SPA site over nginx and some paths that webpack builds like CSS are relative. So the site does not render properly.

@mantou132
Copy link

@thiagoferreiraw
Copy link

thiagoferreiraw commented Nov 18, 2021

For those struggling with the nginx configuration on SPAs (react, angular, vue.js), here's an overview on how it should work:

How the @prerender location works πŸ₯‡ :

Given we have a default location (/) with "try_files $uri @prerender", here's what happens:
First, nginx will try to find a real file on the directory, such as images, js or css files.
  If there is a match, nginx will return that file.
If no real file is found, nginx will try the @prerender location:
  If the user agent is a bot (google, twitter, etc), set a variable $prerender to 1
  If the user agent is prerender itself, set it to 0 back again
  If the uri is for a file such as js, css, imgs, set it to 0 again
  Now, if after all of these conditions we have $prerender==1, we will;
      Proxy pass the request to prerender and return the cached html file
  If we have prerender=0:
      Default to our own index.html.
      Since this is a SPA, all uris that are not a real file will be redirected to index.html

Caveats 🚨

Final nginx conf with SSL πŸ”’

It's very similar to the first example, I just needed to add a root path in each location:

# MANAGED BY PUPPET
server {
  listen       *:443 ssl;


  server_name  example.com;

  ssl_certificate           /etc/nginx/ssl/example.crt;
  ssl_certificate_key       /etc/nginx/ssl/example.key;

  access_log            /var/log/nginx/example.log combined_cloudflare;
  error_log             /var/log/nginx/ssl-example.error.log;
      add_header "X-Clacks-Overhead" "GNU Terry Pratchett";


  location / {
    root      /usr/local/example/webapp-build;
    try_files $uri @prerender;
    add_header Cache-Control "no-cache";
  }
  # How the @prerender location works:
  # Given we have a default location (/) with "try_files $uri @prerender", here's what happens:
  # First, nginx will try to find a real file on the directory, such as images, js or css files.
  #   If there is a match, nginx will return that file.
  # If no real file is found, nginx will try the @prerender location:
  #   If the user agent is a bot (google, twitter, etc), set a variable $prerender to 1
  #   If the user agent is prerender itself, set it to 0 back again
  #   If the uri is for a file such as js, css, imgs, set it to 0 again
  #   Now, if after all of these conditions we have $prerender==1, we will;
  #       Proxy pass the request to prerender and return the cached html file
  #   If we have prerender=0:
  #       Default to our own index.html.
  #       Since this is a SPA, all uris that are not a real file will be redirected to index.html
  # **** CAVEATS ****
  # - The `if` directive in nginx can be tricky (please read https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/)
  # - In summary, we should never use try_files and ifs in the same location block.
  # - This is the reason why we have to use rewrite on the @location block instead of try_files.

  location @prerender {
    root      /usr/local/example/webapp-build;
    proxy_set_header X-Prerender-Token "YOUR_TOKEN_GOES_HERE";

    set $prerender 0;
    if ($http_user_agent ~* "googlebot|bingbot|yandex|baiduspider|twitterbot|facebookexternalhit|rogerbot|linkedinbot|embedly|quora link preview|showyoubot|outbrain|pinterest\/0\.|pinterestbot|slackbot|vkShare|W3C_Validator|whatsapp") {
        set $prerender 1;
    }
    if ($args ~ "_escaped_fragment_") {
        set $prerender 1;
    }
    if ($http_user_agent ~ "Prerender") {
        set $prerender 0;
    }
    if ($uri ~* "\.(js|css|xml|less|png|jpg|jpeg|gif|pdf|doc|txt|ico|rss|zip|mp3|rar|exe|wmv|doc|avi|ppt|mpg|mpeg|tif|wav|mov|psd|ai|xls|mp4|m4a|swf|dat|dmg|iso|flv|m4v|torrent|ttf|woff|svg|eot)") {
        set $prerender 0;
    }

    #resolve using Google's DNS server to force DNS resolution and prevent caching of IPs
    resolver 8.8.8.8;

    if ($prerender = 1) {
        #setting prerender as a variable forces DNS resolution since nginx caches IPs and doesnt play well with load balancing
        set $prerender "service.prerender.io";
        rewrite .* /$scheme://$host$request_uri? break;
        proxy_pass http://$prerender;
        break;
    }

    rewrite .* /index.html break;
  }
}

If you are using puppet to manage your infrastructure... πŸ€–

I preferred creating a template file with the @prerender location for readability. Additionally, this config should only be done in production, so I added some conditionals as well:

  if $environment == 'production' {
    # prerender.io is a SEO tool used to process javascript websites into a robot-friendly page.
    # Please read the details on profile/webapp/prerender_nginx.erb
    $nginx_try_files = ['$uri', '@prerender']  # the $uri must be single quoted!
    $nginx_raw_append = template('profile/webapp/prerender_nginx.erb')  # Only the @prerender location goes in this block
  } else {
    $nginx_try_files = ['$uri', '/index.html']  # the $uri must be single quoted!
    $nginx_raw_append = []
  }

nginx::resource::server { $name:
    ensure                => present,
    server_name           => $server_name,
    listen_port           => 443,
    ssl_port              => 443,
    ssl                   => true,
    ssl_cert              => $ssl_cert,
    ssl_key               => $ssl_key,
    format_log            => 'combined_cloudflare',
    www_root              => $www_root,
    index_files           => $index_files,
    error_pages           => $error_pages,
    try_files             => $nginx_try_files,
    location_cfg_append   => $location_cfg_append,
    raw_append            => $nginx_raw_append,
  }
```

@varrocs
Copy link

varrocs commented Nov 18, 2021

Hi everyone!
there is an updated, official set of nginx configuration files here:
https://github.com/prerender/prerender-nginx

@zehawki
Copy link

zehawki commented Mar 17, 2022

Does anyone know what should be the UA string for Google Sites (https://sites.google.com)? Perhaps they use something else other than 'googlebot' since I can't get link unfurling to work when using their "Embed from the web" widget.

@Norlandz
Copy link

I need to use rewrite .* /app/index.html last; instead of rewrite .* /app/index.html break; idk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment