return fmt.Sprintf("%s(%q, %t){%s}", sc.name, params, sc.doMarkup, sc.inner)
 }
 
+type shortcodeHandler struct {
+       // Maps the shortcodeplaceholder with the shortcode rendering func.
+       contentShortCodes map[string]func() (string, error)
+
+       // Maps the shortcodeplaceholder with the actual shortcode.
+       shortcodes map[string]shortcode
+
+       // All the shortcode names in this set.
+       nameSet map[string]bool
+}
+
+func newShortcodeHandler() *shortcodeHandler {
+       return &shortcodeHandler{
+               contentShortCodes: make(map[string]func() (string, error)),
+               shortcodes:        make(map[string]shortcode),
+               nameSet:           make(map[string]bool),
+       }
+}
+
+// TODO(bep) make it non-global
 var isInnerShortcodeCache = struct {
        sync.RWMutex
        m map[string]bool
        return match, nil
 }
 
+func clearIsInnerShortcodeCache() {
+       isInnerShortcodeCache.Lock()
+       defer isInnerShortcodeCache.Unlock()
+       isInnerShortcodeCache.m = make(map[string]bool)
+}
+
 func createShortcodePlaceholder(id int) string {
        return fmt.Sprintf("HAHA%s-%dHBHB", shortcodePlaceholderPrefix, id)
 }
        tmpl := getShortcodeTemplate(sc.name, p.s.Tmpl)
 
        if tmpl == nil {
-               p.s.Log.ERROR.Printf("Unable to locate template for shortcode '%s' in page %s", sc.name, p.BaseFileName())
+               p.s.Log.ERROR.Printf("Unable to locate template for shortcode '%s' in page %q", sc.name, p.Path())
                return ""
        }
 
                        case shortcode:
                                inner += renderShortcode(innerData.(shortcode), data, p)
                        default:
-                               p.s.Log.ERROR.Printf("Illegal state on shortcode rendering of '%s' in page %s. Illegal type in inner data: %s ",
-                                       sc.name, p.BaseFileName(), reflect.TypeOf(innerData))
+                               p.s.Log.ERROR.Printf("Illegal state on shortcode rendering of %q in page %q. Illegal type in inner data: %s ",
+                                       sc.name, p.Path(), reflect.TypeOf(innerData))
                                return ""
                        }
                }
        return renderShortcodeWithPage(tmpl, data)
 }
 
-func extractAndRenderShortcodes(stringToParse string, p *Page) (string, map[string]func() (string, error), error) {
-
-       content, shortcodes, err := extractShortcodes(stringToParse, p)
+func (s *shortcodeHandler) extractAndRenderShortcodes(stringToParse string, p *Page) (string, error) {
+       content, err := s.extractShortcodes(stringToParse, p)
 
        if err != nil {
                //  try to render what we have whilst logging the error
                p.s.Log.ERROR.Println(err.Error())
        }
 
-       // Save for reuse
-       // TODO(bep) refactor this
-       p.shortcodes = shortcodes
-
-       renderedShortcodes := renderShortcodes(shortcodes, p)
+       s.contentShortCodes = renderShortcodes(s.shortcodes, p)
 
-       return content, renderedShortcodes, err
+       return content, err
 
 }
 
 // pageTokens state:
 // - before: positioned just before the shortcode start
 // - after: shortcode(s) consumed (plural when they are nested)
-func extractShortcode(pt *pageTokens, p *Page) (shortcode, error) {
+func (s *shortcodeHandler) extractShortcode(pt *pageTokens, p *Page) (shortcode, error) {
        sc := shortcode{}
        var isInner = false
 
                        if cnt > 0 {
                                // nested shortcode; append it to inner content
                                pt.backup3(currItem, next)
-                               nested, err := extractShortcode(pt, p)
+                               nested, err := s.extractShortcode(pt, p)
+                               if nested.name != "" {
+                                       s.nameSet[nested.name] = true
+                               }
                                if err == nil {
                                        sc.inner = append(sc.inner, nested)
                                } else {
                case tScName:
                        sc.name = currItem.val
                        tmpl := getShortcodeTemplate(sc.name, p.s.Tmpl)
-
+                       {
+                       }
                        if tmpl == nil {
-                               return sc, fmt.Errorf("Unable to locate template for shortcode '%s' in page %s", sc.name, p.BaseFileName())
+                               return sc, fmt.Errorf("Unable to locate template for shortcode %q in page %q", sc.name, p.Path())
                        }
 
                        var err error
                        isInner, err = isInnerShortcode(tmpl)
                        if err != nil {
-                               return sc, fmt.Errorf("Failed to handle template for shortcode '%s' for page '%s': %s", sc.name, p.BaseFileName(), err)
+                               return sc, fmt.Errorf("Failed to handle template for shortcode %q for page %q: %s", sc.name, p.Path(), err)
                        }
 
                case tScParam:
        return sc, nil
 }
 
-func extractShortcodes(stringToParse string, p *Page) (string, map[string]shortcode, error) {
-
-       shortCodes := make(map[string]shortcode)
+func (s *shortcodeHandler) extractShortcodes(stringToParse string, p *Page) (string, error) {
 
        startIdx := strings.Index(stringToParse, "{{")
 
        // short cut for docs with no shortcodes
        if startIdx < 0 {
-               return stringToParse, shortCodes, nil
+               return stringToParse, nil
        }
 
        // the parser takes a string;
        // … it's safe to keep some "global" state
        var currItem item
        var currShortcode shortcode
-       var err error
 
 Loop:
        for {
                case tLeftDelimScWithMarkup, tLeftDelimScNoMarkup:
                        // let extractShortcode handle left delim (will do so recursively)
                        pt.backup()
-                       if currShortcode, err = extractShortcode(pt, p); err != nil {
-                               return result.String(), shortCodes, err
+
+                       currShortcode, err := s.extractShortcode(pt, p)
+
+                       if currShortcode.name != "" {
+                               s.nameSet[currShortcode.name] = true
+                       }
+
+                       if err != nil {
+                               return result.String(), err
                        }
 
                        if currShortcode.params == nil {
 
                        placeHolder := createShortcodePlaceholder(id)
                        result.WriteString(placeHolder)
-                       shortCodes[placeHolder] = currShortcode
+                       s.shortcodes[placeHolder] = currShortcode
                        id++
                case tEOF:
                        break Loop
                        err := fmt.Errorf("%s:%d: %s",
                                p.FullFilePath(), (p.lineNumRawContentStart() + pt.lexer.lineNum() - 1), currItem)
                        currShortcode.err = err
-                       return result.String(), shortCodes, err
+                       return result.String(), err
                }
        }
 
-       return result.String(), shortCodes, nil
+       return result.String(), nil
 
 }
 
 
        tmplChanged := []fsnotify.Event{}
        dataChanged := []fsnotify.Event{}
        i18nChanged := []fsnotify.Event{}
-
+       shortcodesChanged := make(map[string]bool)
        // prevent spamming the log on changes
        logger := helpers.NewDistinctFeedbackLogger()
 
                if s.isLayoutDirEvent(ev) {
                        logger.Println("Template changed", ev.Name)
                        tmplChanged = append(tmplChanged, ev)
+
+                       if strings.Contains(ev.Name, "shortcodes") {
+                               clearIsInnerShortcodeCache()
+                               shortcode := filepath.Base(ev.Name)
+                               shortcode = strings.TrimSuffix(shortcode, filepath.Ext(shortcode))
+                               shortcodesChanged[shortcode] = true
+                       }
                }
                if s.isDataDirEvent(ev) {
                        logger.Println("Data changed", ev.Name)
 
        }
 
+       for shortcode, _ := range shortcodesChanged {
+               // There are certain scenarios that, when a shortcode changes,
+               // it isn't sufficient to just rerender the already parsed shortcode.
+               // One example is if the user adds a new shortcode to the content file first,
+               // and then creates the shortcode on the file system.
+               // To handle these scenarios, we must do a full reprocessing of the
+               // pages that keeps a reference to the changed shortcode.
+               pagesWithShortcode := s.findPagesByShortcode(shortcode)
+               for _, p := range pagesWithShortcode {
+                       p.rendered = false
+                       pageChan <- p
+               }
+       }
+
        // we close the filechan as we have sent everything we want to send to it.
        // this will tell the sourceReaders to stop iterating on that channel
        close(filechan)